/* xsane -- a graphical (X11, gtk) scanner-oriented SANE frontend

   xsane.c

   Oliver Rauch <Oliver.Rauch@rauch-domain.de>
   Copyright (C) 1998-2007 Oliver Rauch
   This file is part of the XSANE package.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */ 

/* ---------------------------------------------------------------------------------------------------------------------- */

#include "xsane.h"
#include "xsane-back-gtk.h"
#include "xsane-front-gtk.h"
#include "xsane-preview.h"
#include "xsane-save.h"
#include "xsane-gamma.h"
#include "xsane-setup.h"
#include "xsane-scan.h"
#include "xsane-multipage-project.h"
#include "xsane-fax-project.h"
#include "xsane-email-project.h"
#include "xsane-rc-io.h"
#include "xsane-device-preferences.h"
#include "xsane-preferences.h"
#include "xsane-icons.h"
#include "xsane-batch-scan.h"
#include "xsane-curve.h"
#include "xsane-gammacurve.h"
#include "xsane-main-window.h"
#include "xsane-ui.h"

#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
#include <png.h>
#include <zlib.h>
#endif
#endif

#include <sys/wait.h>

/* ---------------------------------------------------------------------------------------------------------------------- */

struct option long_options[] =
{
  {"help", no_argument, 0, 'h'},
  {"version", no_argument, 0, 'v'},
  {"license", no_argument, 0, 'l'},
  {"device-settings", required_argument, 0, 'd'},
  {"xsane-rc", required_argument, 0, 'r'},
  {"save", no_argument, 0, 's'},
  {"viewer", no_argument, 0, 'V'},
  {"copy", no_argument, 0, 'c'},
  {"fax", no_argument, 0, 'f'},
  {"multipage", no_argument, 0, 'm'},
#ifdef XSANE_ACTIVATE_EMAIL
  {"email", no_argument, 0, 'e'},
#endif
  {"no-mode-selection", no_argument, 0, 'n'},
  {"Fixed", no_argument, 0, 'F'},
  {"Resizeable", no_argument, 0, 'R'},
  {"print-filenames", no_argument, 0, 'p'},
  {"force-filename", required_argument, 0, 'N'},
  {0, }
};

/* ---------------------------------------------------------------------------------------------------------------------- */

static const pref_default_preset_area_t pref_default_preset_area[] =
{
 { MENU_ITEM_SURFACE_FULL_SIZE, 0,      0,      INF,    INF },
 { MENU_ITEM_SURFACE_DIN_A3P,   0,      0,      296.98, 420.0 },
 { MENU_ITEM_SURFACE_DIN_A3L,   0,      0,      420.0,  296.98 },
 { MENU_ITEM_SURFACE_DIN_A4P,   0,      0,      210.0,  296.98 },
 { MENU_ITEM_SURFACE_DIN_A4L,   0,      0,      296.98, 210.0 },
 { MENU_ITEM_SURFACE_DIN_A5P,   0,      0,      148.5,  210.0 },
 { MENU_ITEM_SURFACE_DIN_A5L,   0,      0,      210.0,  148.5 },
 { MENU_ITEM_SURFACE_13cmx18cm, 0,      0,      130.0,  180.0 },
 { MENU_ITEM_SURFACE_18cmx13cm, 0,      0,      180.0,  130.0 },
 { MENU_ITEM_SURFACE_10cmx15cm, 0,      0,      100.0,  150.0 },
 { MENU_ITEM_SURFACE_15cmx10cm, 0,      0,      150.0,  100.0 },
 { MENU_ITEM_SURFACE_9cmx13cm,  0,      0,      90.0,   130.0 },
 { MENU_ITEM_SURFACE_13cmx9cm,  0,      0,      130.0,  90.0 },
 { MENU_ITEM_SURFACE_legal_P,   0,      0,      215.9,  355.6 },
 { MENU_ITEM_SURFACE_legal_L,   0,      0,      355.6,  215.9 },
 { MENU_ITEM_SURFACE_letter_P,  0,      0,      215.9,  279.4 },
 { MENU_ITEM_SURFACE_letter_L,  0,      0,      279.4,  215.9 },
};

/* ---------------------------------------------------------------------------------------------------------------------- */

static const Preferences_medium_t pref_default_medium[]=
{
/* medium				shadow                  highlight                gamma                negative */
/* name					gray  red   green blue  gray  red   green blue   gray  red   gren  blue */
 { MENU_ITEM_MEDIUM_FULL_COLOR_RANGE,	 0.0,  0.0,  0.0,  0.0, 100.0,100.0,100.0,100.0, 1.00, 1.00, 1.00, 1.00 , 0},
 { MENU_ITEM_MEDIUM_SLIDE,		 0.0,  0.0,  0.0,  0.0,  40.0, 40.0, 40.0, 40.0, 1.00, 1.00, 1.00, 1.00 , 0},
 { MENU_ITEM_MEDIUM_STANDARD_NEG,	 0.0,  7.0,  1.0,  0.0,  66.0, 66.0, 33.0, 16.0, 1.00, 1.00, 1.00, 1.00 , 1},
 { MENU_ITEM_MEDIUM_AGFA_NEG,		 0.0,  6.0,  2.0,  0.0,  31.0, 61.0, 24.0, 13.0, 1.00, 1.00, 1.00, 1.00 , 1},
 { MENU_ITEM_MEDIUM_AGFA_NEG_XRG200_4,	 0.0, 12.0,  2.0,  1.6,  35.0, 61.5, 21.5, 14.5, 1.00, 0.80, 0.67, 0.60 , 1},
 { MENU_ITEM_MEDIUM_AGFA_NEG_HDC_100,	 0.0,  3.5,  1.0,  0.5,  26.5, 53.5, 22.0, 17.0, 1.00, 0.79, 0.65, 0.60 , 1},
 { MENU_ITEM_MEDIUM_FUJI_NEG,		 0.0,  7.0,  1.0,  0.0,  32.0, 64.0, 33.0, 16.0, 1.00, 1.00, 1.00, 1.00 , 1},
 { MENU_ITEM_MEDIUM_KODAK_NEG,		 0.0,  9.0,  2.0,  0.0,  27.0, 54.0, 18.0, 12.0, 1.00, 1.00, 1.00, 1.00 , 1},
 { MENU_ITEM_MEDIUM_KONICA_NEG,		 0.0,  3.0,  0.0,  0.0,  25.0, 38.0, 21.0, 14.0, 1.00, 1.00, 1.00, 1.00 , 1},
 { MENU_ITEM_MEDIUM_KONICA_NEG_VX_100,	 0.0,  2.0,  0.0,  0.0,  25.0, 46.0, 22.0, 13.5, 1.00, 0.74, 0.56, 0.53 , 1},
 { MENU_ITEM_MEDIUM_ROSSMANN_NEG_HR_100, 0.0,  7.0,  1.0,  1.6,  26.5, 58.0, 25.5, 19.0, 1.00, 0.54, 0.43, 0.41 , 1}
};

int DBG_LEVEL = 0;

/* ---------------------------------------------------------------------------------------------------------------------- */

struct Xsane xsane;				/* most xsane dependant values */

/* ---------------------------------------------------------------------------------------------------------------------- */

/* forward declarations: */

static void xsane_zoom_update(GtkAdjustment *adj_data, double *val);
static void xsane_show_device_options_callback(GtkWidget *widget);
static void xsane_show_gamma_callback(GtkWidget *widget);
static void xsane_show_batch_scan_callback(GtkWidget *widget);
void xsane_pref_save(void);
static int xsane_pref_restore(void);
static void xsane_pref_save_media(void);
static void xsane_pref_restore_media(void);
static RETSIGTYPE xsane_quit_handler(int signal);
static RETSIGTYPE xsane_sigchld_handler(int signal);
void xsane_quit(void);
void xsane_exit(void);
static void xsane_set_pref_unit_callback(GtkWidget *widget, gpointer data);
static void xsane_edit_medium_definition_callback(GtkWidget *widget, gpointer data);
static void xsane_set_update_policy_callback(GtkWidget *widget, gpointer data);
static gint xsane_close_info_callback(GtkWidget *widget, gpointer data);
static void xsane_info_dialog(GtkWidget *widget, gpointer data);
static void xsane_about_dialog(GtkWidget *widget, gpointer data);
static void xsane_about_translation_dialog(GtkWidget *widget, gpointer data);
static void xsane_pref_toggle_tooltips(GtkWidget *widget, gpointer data);
static void xsane_show_eula(GtkWidget *widget, gpointer data);
static void xsane_show_gpl(GtkWidget *widget, gpointer data);
static void xsane_show_doc(GtkWidget *widget, gpointer data);
//static void xsane_device_dialog(void);
static void xsane_choose_dialog_ok_callback(void);
static void xsane_select_device_by_key_callback(GtkWidget * widget, gpointer data);
static int xsane_select_device_by_mouse_callback(GtkWidget * widget, GdkEventButton *event, gpointer data);
static void xsane_choose_device(void);
static void xsane_usage(void);
static int xsane_init(int argc, char **argv);
void xsane_interface(int argc, char **argv);
int main(int argc, char ** argv);

/* ---------------------------------------------------------------------------------------------------------------------- */

#ifndef __GNUC__
void xsane_debug_message(int level, const char *fmt, ...)
{
  if (DBG_LEVEL >= level)
  {
    va_list ap;
    fprintf(stderr, "[xsane] ");
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
  }
}
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_threshold_changed()
{
  DBG(DBG_proc, "xsane_threshold_changed\n");

  if (xsane.param.depth == 1) /* lineart mode */
  {
    if ( (xsane.lineart_mode == XSANE_LINEART_GRAYSCALE) || (xsane.lineart_mode == XSANE_LINEART_XSANE) )
    {
     const SANE_Option_Descriptor *opt = xsane_get_option_descriptor(xsane.device_options.dev, xsane.device_options.well_known.threshold);

      if (opt)
      {
       SANE_Word threshold_value;
       double threshold = xsane.threshold * xsane.threshold_mul + xsane.threshold_off;

        if (opt->type == SANE_TYPE_FIXED)
        {
          threshold_value = SANE_FIX(threshold);
        }
        else
        {
          threshold_value = (int) threshold;
        }

        xsane_back_gtk_set_option(xsane.device_options.well_known.threshold, &threshold_value, SANE_ACTION_SET_VALUE);
      }
    }
  }
}
/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_gamma_changed(GtkAdjustment *adj_data, double *val)
{
  DBG(DBG_proc, "xsane_gamma_changed\n");

  *val = adj_data->value;

  if (!xsane.block_enhancement_update)
  {
    xsane_enhancement_by_gamma();
    xsane_threshold_changed();
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_set_modus_defaults(void)
{
  DBG(DBG_proc, "xsane_set_modus_defaults\n");

  if (xsane.mode == XSANE_GIMP_EXTENSION)
  {
    xsane_define_maximum_output_size();
  }
  else switch(xsane.xsane_mode)
  {
    case XSANE_VIEWER:
      xsane_define_maximum_output_size();
     break;

    case XSANE_SAVE:
      xsane_define_maximum_output_size();
     break;

    case XSANE_COPY: /* set zoomfactor to 1.0 and select full preview area */
      {
       int printer_resolution;

        switch (xsane.param.format)
        {
          case SANE_FRAME_GRAY:
            if (xsane.param.depth == 1)
            {
              printer_resolution = preferences.printer[preferences.printernr]->lineart_resolution;
            }
            else
            {
              printer_resolution = preferences.printer[preferences.printernr]->grayscale_resolution;
            }
          break;

          case SANE_FRAME_RGB:
          case SANE_FRAME_RED:
          case SANE_FRAME_GREEN:
          case SANE_FRAME_BLUE:
          default:
            printer_resolution = preferences.printer[preferences.printernr]->color_resolution;
          break;
        }    
        xsane.zoom   = 1.0;

        xsane.resolution   = xsane.zoom * printer_resolution;
        xsane.resolution_x = xsane.zoom * printer_resolution;
        xsane.resolution_y = xsane.zoom * printer_resolution;

        xsane_set_all_resolutions();
        xsane_define_maximum_output_size(); /* must come before select_full_preview_area */
        preview_select_full_preview_area(xsane.preview);
      }
     break;

    case XSANE_FAX:
      /* select full preview area */
      xsane_define_maximum_output_size(); /* must come before select_full_preview_area */
      preview_select_full_preview_area(xsane.preview);
     break;

    case XSANE_EMAIL:
      xsane_define_maximum_output_size();
     break;

    default:
      xsane_define_maximum_output_size();
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_modus_callback(GtkWidget *xsane_parent, int *num)
{
  DBG(DBG_proc, "xsane_modus_callback\n");

  xsane.xsane_mode = *num;
  preferences.xsane_mode = *num;

  if ((xsane.xsane_mode == XSANE_SAVE) || (xsane.xsane_mode == XSANE_VIEWER) || (xsane.xsane_mode == XSANE_COPY))
  {
    gtk_widget_set_sensitive(xsane.start_button, TRUE); 
  }

#ifdef XSANE_ACTIVATE_EMAIL
  if (xsane.xsane_mode != XSANE_EMAIL)
  {
    if (xsane.email_project_save)
    {
      xsane.email_project_save = 0;
      xsane_email_project_save();
    }
  }
#endif

  xsane_project_dialog_close(); /* close project dialog when one is open */
  xsane_set_modus_defaults(); /* set defaults and maximum output size */
  xsane_refresh_dialog();

  /* make sure dialogs are rebuild, otherwise we can get in trouble */
  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_enhancement_negative_callback(GtkWidget * widget)
{
 double v0;

  DBG(DBG_proc, "xsane_enhancement_negative_callback\n");

  if (xsane.negative != (GTK_TOGGLE_BUTTON(widget)->active != 0))
  {
    v0 = xsane.slider_gray.value[0];
    xsane.slider_gray.value[0] = 100.0 - xsane.slider_gray.value[2];
    xsane.slider_gray.value[1] = 100.0 - xsane.slider_gray.value[1];
    xsane.slider_gray.value[2] = 100.0 - v0;

    if (!xsane.enhancement_rgb_default)
    {
      v0 = xsane.slider_red.value[0];
      xsane.slider_red.value[0] = 100.0 - xsane.slider_red.value[2];
      xsane.slider_red.value[1] = 100.0 - xsane.slider_red.value[1];
      xsane.slider_red.value[2] = 100.0 - v0;

      v0 = xsane.slider_green.value[0];
      xsane.slider_green.value[0] = 100.0 - xsane.slider_green.value[2];
      xsane.slider_green.value[1] = 100.0 - xsane.slider_green.value[1];
      xsane.slider_green.value[2] = 100.0 - v0;

      v0 = xsane.slider_blue.value[0];
      xsane.slider_blue.value[0] = 100.0 - xsane.slider_blue.value[2];
      xsane.slider_blue.value[1] = 100.0 - xsane.slider_blue.value[1];
      xsane.slider_blue.value[2] = 100.0 - v0;
    }
  }

  xsane.negative = (GTK_TOGGLE_BUTTON(widget)->active != 0);

  xsane_update_sliders();
  xsane_enhancement_by_histogram(TRUE);
  xsane_update_gamma_curve(TRUE /* update raw */);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_auto_enhance()
{
  DBG(DBG_proc, "xsane_auto_enhance\n");

  xsane_calculate_raw_histogram();

  xsane_set_auto_enhancement();
  xsane_enhancement_by_histogram(preferences.auto_enhance_gamma);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_auto_enhancement_callback(GtkWidget * widget)
{
  DBG(DBG_proc, "xsane_auto_enhancement_callback\n");

  xsane_auto_enhance();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_always_auto_enhance_callback(GtkWidget * widget)
{
  DBG(DBG_proc, "xsane_always_auto_enhance_callback\n");

  if (xsane.always_auto_enhance != (GTK_TOGGLE_BUTTON(widget)->active != 0))
  {
    xsane_auto_enhancement_callback(NULL);
  }

  xsane.always_auto_enhance = (GTK_TOGGLE_BUTTON(widget)->active != 0);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_show_device_options_callback(GtkWidget * widget)
{
  DBG(DBG_proc, "xsane_show_device_options_callback\n");

  preferences.show_device_options = (GTK_CHECK_MENU_ITEM(widget)->active != 0);
  if (preferences.show_device_options)
  {
    gtk_widget_show(xsane.device_options.window);
  }
  else
  {
    gtk_widget_hide(xsane.device_options.window);
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_show_resolution_list_callback(GtkWidget *widget)
{
  DBG(DBG_proc, "xsane_show_resolution_list_callback\n");

  preferences.show_resolution_list = (GTK_CHECK_MENU_ITEM(widget)->active != 0);

  xsane_refresh_dialog();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_show_histogram_callback(GtkWidget * widget)
{
  DBG(DBG_proc, "xsane_show_histogram_callback\n");

  preferences.show_histogram = !gtk_expander_get_expanded(GTK_EXPANDER(widget));
  if (preferences.show_histogram)
  {
    xsane_update_histogram(TRUE /* update raw */);
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_show_gamma_callback(GtkWidget *widget)
{
  DBG(DBG_proc, "xsane_show_gamma_callback\n");

  preferences.show_gamma = (GTK_CHECK_MENU_ITEM(widget)->active != 0);
  if (preferences.show_gamma)
  {
    xsane_update_gamma_dialog();
    gtk_widget_show(xsane.free_gamma.window);
  }
  else
  {
    gtk_widget_hide(xsane.free_gamma.window);
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_show_batch_scan_callback(GtkWidget * widget)
{
  DBG(DBG_proc, "xsane_show_batch_scan_callback\n");

  preferences.show_batch_scan = (GTK_CHECK_MENU_ITEM(widget)->active != 0);
  if (preferences.show_batch_scan)
  {
    gtk_widget_show(xsane.batch_scan_dialog);
  }
  else
  {
    gtk_widget_hide(xsane.batch_scan_dialog);
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_paper_orientation_callback(GtkWidget *widget, gpointer data)
{
 int pos = (long) data;

  DBG(DBG_proc, "xsane_paper_orientation_callback\n");

  preferences.paper_orientation = pos;
  xsane_define_maximum_output_size();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_printer_callback(GtkWidget *widget, gpointer data)
{
 int printer_resolution; 

  DBG(DBG_proc, "xsane_printer_callback\n");

  preferences.printernr = (long) data;

  switch (xsane.param.format)
  {
    case SANE_FRAME_GRAY:
      if (xsane.param.depth == 1)
      {
        printer_resolution = preferences.printer[preferences.printernr]->lineart_resolution;
      }
      else
      {
        printer_resolution = preferences.printer[preferences.printernr]->grayscale_resolution;
      }
    break;

    case SANE_FRAME_RGB:
    case SANE_FRAME_RED:
    case SANE_FRAME_GREEN:
    case SANE_FRAME_BLUE:
    default:
      printer_resolution = preferences.printer[preferences.printernr]->color_resolution;
    break;
  }       

  xsane.resolution   = xsane.zoom * printer_resolution;
  xsane.resolution_x = xsane.zoom * printer_resolution;
  xsane.resolution_y = xsane.zoom * printer_resolution;

  xsane_set_all_resolutions();
  xsane_define_maximum_output_size();
  xsane_back_gtk_refresh_dialog();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_zoom_update(GtkAdjustment *adj, double *val)
{
 int printer_resolution;

  DBG(DBG_proc, "xsane_zoom_update\n");

  *val=adj->value;

  switch (xsane.param.format)
  {
    case SANE_FRAME_GRAY:
      if (xsane.param.depth == 1)
      {
        printer_resolution = preferences.printer[preferences.printernr]->lineart_resolution;
      }
      else
      {
        printer_resolution = preferences.printer[preferences.printernr]->grayscale_resolution;
      }
    break;

    case SANE_FRAME_RGB:
    case SANE_FRAME_RED:
    case SANE_FRAME_GREEN:
    case SANE_FRAME_BLUE:
    default:
      printer_resolution = preferences.printer[preferences.printernr]->color_resolution;
    break;
  }       

  /* update all resolutions */
  xsane.resolution   = xsane.zoom * printer_resolution;
  xsane.resolution_x = xsane.zoom * printer_resolution;
  xsane.resolution_y = xsane.zoom * printer_resolution;

  xsane_set_all_resolutions();

  xsane_update_param(0);

  xsane_define_maximum_output_size();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_get_zoom_range(int printer_resolution, int well_known_option, double *min, double *max)
{
 const SANE_Option_Descriptor *opt;

  DBG(DBG_proc, "xsane_get_zoom_range\n");

  opt = xsane_get_option_descriptor(xsane.device_options.dev, well_known_option);
  if (!opt)
  {
    return -1; /* option not available */
  }
  else
  {
    if (SANE_OPTION_IS_ACTIVE(opt->cap))
    {
      xsane_get_bounds(opt, min, max);
      *min = *min / printer_resolution;
      *max = *max / printer_resolution;

      return 0; /* everything is ok */
    }
    return 1; /* option not active */
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int xsane_zoom_widget_new(GtkWidget *parent)
{
 const SANE_Option_Descriptor *opt;
 int printer_resolution;
 double min = 0.0;
 double max = 0.0;
 double min2 = 0.0;
 double max2 = 0.0;
 int well_known_option = 0;

  DBG(DBG_proc, "xsane_zoom_widget_new\n");

  switch (xsane.param.format)
  {
    case SANE_FRAME_GRAY:
      if (xsane.param.depth == 1)
      {
        printer_resolution = preferences.printer[preferences.printernr]->lineart_resolution;
      }
      else
      {
        printer_resolution = preferences.printer[preferences.printernr]->grayscale_resolution;
      }
    break;

    case SANE_FRAME_RGB:
    case SANE_FRAME_RED:
    case SANE_FRAME_GREEN:
    case SANE_FRAME_BLUE:
    default:
      printer_resolution = preferences.printer[preferences.printernr]->color_resolution;
    break;
  }       

  if (!xsane_get_zoom_range(printer_resolution, xsane.device_options.well_known.dpi, &min, &max)) /* we have well_known.dpi: */
  {
    well_known_option = xsane.device_options.well_known.dpi;
  }
  else
  {
    if (!xsane_get_zoom_range(printer_resolution, xsane.device_options.well_known.dpi_x, &min, &max)) /* we have well.known.dpi_x */
    {
      well_known_option = xsane.device_options.well_known.dpi_x;
      if (!xsane_get_zoom_range(printer_resolution, xsane.device_options.well_known.dpi_y, &min2, &max2)) /* we have well.known.dpi_y */
      {
        if (min < min2)
        {
          min = min2;
        }

        if (max > max2)
        {
          max = max2;
        }
      }
    }
    else
    {
      return -1; /* no zoom widget created */
    }
  }

  if (min < 0.01)
  {
    min = 0.01;
  }

  opt = xsane_get_option_descriptor(xsane.device_options.dev, well_known_option);
  xsane_range_new_with_pixmap(xsane.main_window->window, GTK_BOX(parent), zoom_xpm, DESC_ZOOM, min, max, 0.01, 0.1, 2,
                              &xsane.zoom, &xsane.zoom_widget, well_known_option, (void *)xsane_zoom_update,
                              SANE_OPTION_IS_SETTABLE(opt->cap));

  xsane.resolution   = xsane.zoom * printer_resolution;
  xsane.resolution_x = xsane.zoom * printer_resolution;
  xsane.resolution_y = xsane.zoom * printer_resolution;

  xsane_set_all_resolutions();

 return 0; /* everything ok */
}

/* ----------------------------------------------------------------------------------------------------------------- */

void xsane_cms_function_menu_callback(GtkWidget *widget, gpointer data)
{
  preferences.cms_function = (long) data;
  DBG(DBG_proc, "xsane_cms_function_menu_callback(%d)\n", preferences.cms_function);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_adf_pages_max_callback(GtkWidget *widget, gpointer data)
{
  xsane_update_int(xsane.adf_pages_max_entry, &preferences.adf_pages_max); /* get adf pages maximum */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_pref_save(void)
{
 char filename[PATH_MAX];
 int fd;

  DBG(DBG_proc, "xsane_pref_save\n");

  /* first save xsane-specific preferences: */
  xsane_back_gtk_make_path(sizeof(filename), filename, "xsaner", NULL, NULL, xsane.xsane_rc_set_filename, ".rc", XSANE_PATH_LOCAL_SANE);

  DBG(DBG_info2, "saving preferences to \"%s\"\n", filename);

  umask(XSANE_DEFAULT_UMASK); /* define new file permissions */    
  fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600); /* rw- --- --- */

  if (fd < 0)
  {
   char buf[TEXTBUFSIZE];

    snprintf(buf, sizeof(buf), "%s %s.", ERR_FAILED_CREATE_FILE, strerror(errno));
    xsane_back_gtk_error(buf, TRUE);
    return;
  }
  preferences_save(fd);
  close(fd);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_pref_restore(void)
/* returns true if this is the first time this xsane version is called */
{
 char filename[PATH_MAX];
 int fd;
 int result = TRUE;
 int i;

  DBG(DBG_proc, "xsane_pref_restore\n");

  xsane_back_gtk_make_path(sizeof(filename), filename, "xsaner", NULL, NULL, xsane.xsane_rc_set_filename, ".rc", XSANE_PATH_LOCAL_SANE);
  fd = open(filename, O_RDONLY);

  if (fd >= 0)
  {
    preferences_restore(fd);
    close(fd);

    /* the version test only is done for the local xsane.rc file because each user */
    /* shall accept (or not) the license for xsane */
    if (preferences.xsane_version_str)
    {
      if (!strcmp(preferences.xsane_version_str, XSANE_VERSION))
      {
        result = FALSE; /* this version already has been started */
      }
    }
  }
  else /* no local sane file, look for system file */
  {
    xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane", 0, ".rc", XSANE_PATH_SYSTEM);
    fd = open(filename, O_RDONLY);

    if (fd >= 0)
    {
      preferences_restore(fd);
      close(fd);
    }
  }

  if (!preferences.preset_area_definitions)
  {
    DBG(DBG_info, "no preset area definitions in preferences file, using predefined list\n");
 
    preferences.preset_area_definitions = sizeof(pref_default_preset_area)/sizeof(pref_default_preset_area_t);
    preferences.preset_area = (Preferences_preset_area_t**)calloc(preferences.preset_area_definitions, sizeof(void *));
 
    for (i=0; i<preferences.preset_area_definitions; i++)
    {
      preferences.preset_area[i] = (Preferences_preset_area_t*)calloc(sizeof(Preferences_preset_area_t), 1);
      preferences.preset_area[i]->name    = strdup(_(pref_default_preset_area[i].name));
      preferences.preset_area[i]->xoffset = pref_default_preset_area[i].xoffset;
      preferences.preset_area[i]->yoffset = pref_default_preset_area[i].yoffset;
      preferences.preset_area[i]->width   = pref_default_preset_area[i].width;
      preferences.preset_area[i]->height  = pref_default_preset_area[i].height;
    }
  }

  if (preferences.xsane_version_str)
  {
    free(preferences.xsane_version_str);
  }
  preferences.xsane_version_str = strdup(XSANE_VERSION); /* store recent xsane-version */

  if (!preferences.tmp_path)
  {
    if (getenv(STRINGIFY(ENVIRONMENT_TEMP_DIR_NAME))) /* if possible get temp path from environment */
    {
      preferences.tmp_path = strdup(getenv(STRINGIFY(ENVIRONMENT_TEMP_DIR_NAME)));
      DBG(DBG_info, "setting temporary directory by environment variable %s: %s\n",
          STRINGIFY(ENVIRONMENT_TEMP_DIR_NAME), preferences.tmp_path);
    }
    else /* otherwise use predefined path */
    {
      preferences.tmp_path = strdup(STRINGIFY(TEMP_PATH));
      DBG(DBG_info, "setting temporary directory to %s\n", preferences.tmp_path);
    }
  }

  if (!preferences.working_directory)
  {
   char filename[PATH_MAX];

    if (getcwd(filename, sizeof(filename)))
    {
      preferences.working_directory = strdup(filename); /* set current working directory */
    }
  }
  else
  {
    chdir(preferences.working_directory); /* set working directory */
  }

  if (!preferences.filename)
  {
   char filename[PATH_MAX];
   char buf[PATH_MAX];

    if (getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)) != NULL)
    {
      snprintf(buf, sizeof(buf)-2, "%s%c%s", getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)), SLASH, OUT_FILENAME);
      preferences.filename = strdup(buf);
    }
    else if (getcwd(filename, sizeof(filename)))
    {
      snprintf(buf, sizeof(buf)-2, "%s%c%s", filename, SLASH, OUT_FILENAME);
      preferences.filename = strdup(buf);
    }
    else
    {
      preferences.filename = strdup("");
    }
  }

  if (!preferences.filetype)
  {
    preferences.filetype = strdup(XSANE_FILETYPE_BY_EXT);
  }

  if (preferences.printerdefinitions == 0)
  {
    xsane_new_printer();
  }

  if (!preferences.fax_project)
  {
   char filename[PATH_MAX];
   char buf[PATH_MAX];

    if (getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)) != NULL)
    {
      snprintf(buf, sizeof(buf)-2, "%s%c%s", getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)), SLASH, FAXPROJECT);
      preferences.fax_project = strdup(buf);
    }
    else if (getcwd(filename, sizeof(filename)))
    {
      snprintf(buf, sizeof(buf)-2, "%s%c%s", filename, SLASH, FAXPROJECT);
      preferences.fax_project = strdup(buf);
    }
    else
    {
      preferences.fax_project = strdup("");
    }
  }

  if (!preferences.fax_command)
  {
    preferences.fax_command = strdup(FAXCOMMAND);
  }

  if (!preferences.fax_receiver_option)
  {
    preferences.fax_receiver_option = strdup(FAXRECEIVEROPT);
  }

  if (!preferences.fax_postscript_option)
  {
    preferences.fax_postscript_option = strdup(FAXPOSTSCRIPTOPT);
  }

  if (!preferences.fax_normal_option)
  {
    preferences.fax_normal_option = strdup(FAXNORMALOPT);
  }

  if (!preferences.fax_fine_option)
  {
    preferences.fax_fine_option = strdup(FAXFINEOPT);
  }

  if (!preferences.fax_viewer)
  {
    preferences.fax_viewer = strdup(FAXVIEWER);
  }

#ifdef XSANE_ACTIVATE_EMAIL
  if (!preferences.email_smtp_server)
  {
    preferences.email_smtp_server = strdup("");
  }

  if (!preferences.email_from)
  {
    preferences.email_from = strdup("");
  }

  if (!preferences.email_reply_to)
  {
    preferences.email_reply_to = strdup("");
  }

  if (!preferences.email_pop3_server)
  {
    preferences.email_pop3_server = strdup("");
  }

  if (!preferences.email_auth_user)
  {
    preferences.email_auth_user = strdup("");
  }

  if (!preferences.email_auth_pass)
  {
    preferences.email_auth_pass = strdup("");
  }

  if (!preferences.email_project)
  {
   char filename[PATH_MAX];
   char buf[PATH_MAX];

    if (getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)) != NULL)
    {
      snprintf(buf, sizeof(buf)-2, "%s%c%s", getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)), SLASH, EMAILPROJECT);
      preferences.email_project = strdup(buf);
    }
    else if (getcwd(filename, sizeof(filename)))
    {
      snprintf(buf, sizeof(buf)-2, "%s%c%s", filename, SLASH, EMAILPROJECT);
      preferences.email_project = strdup(buf);
    }
    else
    {
      preferences.email_project = strdup("");
    }
  }

  if (!preferences.email_filetype)
  {
    preferences.email_filetype = strdup(XSANE_DEFAULT_EMAIL_TYPE);
  }
#endif

  if (!preferences.multipage_project)
  {
   char filename[PATH_MAX];
   char buf[PATH_MAX];

    if (getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)) != NULL)
    {
      snprintf(buf, sizeof(buf)-2, "%s%c%s", getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)), SLASH, MULTIPAGEPROJECT);
      preferences.multipage_project = strdup(buf);
    }
    else if (getcwd(filename, sizeof(filename)))
    {
      snprintf(buf, sizeof(buf)-2, "%s%c%s", filename, SLASH, MULTIPAGEPROJECT);
      preferences.multipage_project = strdup(buf);
    }
    else
    {
      preferences.multipage_project = strdup("");
    }
  }

  if (!preferences.multipage_filetype)
  {
    preferences.multipage_filetype = strdup(MULTIPAGEFILETYPE);
  }

  if (!preferences.ocr_command)
  {
    preferences.ocr_command = strdup(OCRCOMMAND);
  }

  if (!preferences.ocr_inputfile_option)
  {
    preferences.ocr_inputfile_option = strdup(OCRINPUTFILEOPT);
  }

  if (!preferences.ocr_outputfile_option)
  {
    preferences.ocr_outputfile_option = strdup(OCROUTPUTFILEOPT);
  }

  if (!preferences.ocr_gui_outfd_option)
  {
    preferences.ocr_gui_outfd_option = strdup(OCROUTFDOPT);
  }

  if (!preferences.ocr_progress_keyword)
  {
    preferences.ocr_progress_keyword = strdup(OCRPROGRESSKEY);
  }

  if (!preferences.browser)
  {
    if (getenv(STRINGIFY(ENVIRONMENT_BROWSER_NAME)))
    {
      preferences.browser = strdup(getenv(STRINGIFY(ENVIRONMENT_BROWSER_NAME)));
    }
    else
    {
      preferences.browser = strdup(DEFAULT_BROWSER);
    }
  }

  if (xsane.mode != XSANE_GIMP_EXTENSION)
  {
    if (xsane.xsane_mode < 0)
    {
      xsane.xsane_mode = preferences.xsane_mode;
    }
  }

 return result;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_pref_save_media(void)
{
 char filename[PATH_MAX];
 int fd;

  DBG(DBG_proc, "xsane_pref_save_media\n");

  xsane_back_gtk_make_path(sizeof(filename), filename, "xsaner", NULL, "xsaner", NULL, ".mdf", XSANE_PATH_LOCAL_SANE);

  DBG(DBG_info2, "saving media to \"%s\"\n", filename);

  umask(XSANE_DEFAULT_UMASK); /* define new file permissions */    
  fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600); /* rw- --- --- */

  if (fd < 0)
  {
   char buf[TEXTBUFSIZE];

    snprintf(buf, sizeof(buf), "%s %s.", ERR_FAILED_CREATE_FILE, strerror(errno));
    xsane_back_gtk_error(buf, TRUE);
    return;
  }
  preferences_save_media(fd);
  close(fd);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_pref_restore_media(void)
{
 char filename[PATH_MAX];
 int fd;
 int i;

  DBG(DBG_proc, "xsane_pref_restore_media\n");

  preferences.medium_definitions = 0;

  xsane_back_gtk_make_path(sizeof(filename), filename, "xsaner", NULL, "xsaner", NULL, ".mdf", XSANE_PATH_LOCAL_SANE);
  fd = open(filename, O_RDONLY);

  if (fd >= 0)
  {
    preferences_restore_media(fd);
    close(fd);
  }

  if (!preferences.medium_definitions)
  {
    DBG(DBG_info, "no medium definitions found, using predefined list\n");
 
    preferences.medium_definitions = sizeof(pref_default_medium)/sizeof(Preferences_medium_t);
    preferences.medium = (Preferences_medium_t**)calloc(preferences.medium_definitions, sizeof(void *));
 
    for (i=0; i<preferences.medium_definitions; i++)
    {
      preferences.medium[i] = (Preferences_medium_t*)calloc(sizeof(Preferences_medium_t), 1);
      preferences.medium[i]->name            = strdup(_(pref_default_medium[i].name));
      preferences.medium[i]->shadow_gray     = pref_default_medium[i].shadow_gray;
      preferences.medium[i]->shadow_red      = pref_default_medium[i].shadow_red;
      preferences.medium[i]->shadow_green    = pref_default_medium[i].shadow_green;
      preferences.medium[i]->shadow_blue     = pref_default_medium[i].shadow_blue;
      preferences.medium[i]->highlight_gray  = pref_default_medium[i].highlight_gray;
      preferences.medium[i]->highlight_red   = pref_default_medium[i].highlight_red;
      preferences.medium[i]->highlight_green = pref_default_medium[i].highlight_green;
      preferences.medium[i]->highlight_blue  = pref_default_medium[i].highlight_blue;
      preferences.medium[i]->gamma_gray      = pref_default_medium[i].gamma_gray;
      preferences.medium[i]->gamma_red       = pref_default_medium[i].gamma_red;
      preferences.medium[i]->gamma_green     = pref_default_medium[i].gamma_green;
      preferences.medium[i]->gamma_blue      = pref_default_medium[i].gamma_blue;
      preferences.medium[i]->negative        = pref_default_medium[i].negative;
    }
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static RETSIGTYPE xsane_quit_handler(int signal)
{
  DBG(DBG_proc, "xsane_quit_handler\n");

  xsane_quit();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static RETSIGTYPE xsane_sigchld_handler(int signal)
{
 int status;
 XsaneChildprocess **childprocess_listptr = &xsane.childprocess_list;
 XsaneChildprocess *childprocess = xsane.childprocess_list;

  DBG(DBG_proc, "xsane_sigchld_handler\n");

  /* normally we would do a wait(&status); here, but some backends fork() a reader
     process and test the return status of waitpid() that returns with an error
     when we get the signal at first and clean up the process with wait().
     A backend should not handle this as error but some do and so we have to handle this */

  while (childprocess)
  {
   XsaneChildprocess *nextprocess;
   pid_t pid;

    pid = waitpid(childprocess->pid, &status, WNOHANG);
    if ( (WIFEXITED(status)) && (pid == childprocess->pid) )
    {
      DBG(DBG_info, "deleteing pid %d from list\n", childprocess->pid);

      nextprocess = childprocess->next;
      free(childprocess); /* free memory of element */
      childprocess = nextprocess; /* go to next element */
      *childprocess_listptr = childprocess; /* remove element from list */
      /* childprocess_listptr keeps the same !!! */
    }
    else
    {
      DBG(DBG_info, "keeping pid %d in list\n", childprocess->pid);

      childprocess_listptr = &childprocess->next; /* set pointer to next element */
      childprocess = childprocess->next; /* go to next element */
    }
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_quit(void)
{
  DBG(DBG_proc, "xsane_quit\n");

  while (xsane.viewer_list) /* remove all viewer images */
  {
   Viewer *next_viewer = xsane.viewer_list->next_viewer; /* store pointer to next viewer */

    DBG(DBG_info, "removing viewer image %s\n", xsane.viewer_list->filename);
    remove(xsane.viewer_list->filename); /* remove image file */

    gtk_widget_destroy(xsane.viewer_list->top); /* destroy the viewer window */
    free(xsane.viewer_list); /* free memory of struct Viewer */

    xsane.viewer_list = next_viewer;
  }

  if (xsane.preview)
  {
    Preview *preview = xsane.preview;
    xsane.preview = 0;
    preview_destroy(preview);
  }

#ifdef XSANE_ACTIVATE_EMAIL
  if (xsane.email_project_save)
  {
    xsane.email_project_save = 0;
    xsane_email_project_save();
  }
#endif

  while (xsane.back_gtk_message_dialog_active)
  {
    gtk_main_iteration();
  }

  if (xsane.device_options.dev)
  {
    sane_close(xsane.device_options.dev);
  }

  sane_exit();
  gtk_main_quit();

  if (xsane.preview_gamma_data_red)
  {
    free(xsane.preview_gamma_data_red);
    free(xsane.preview_gamma_data_green);
    free(xsane.preview_gamma_data_blue);

    xsane.preview_gamma_data_red   = 0;
    xsane.preview_gamma_data_green = 0;
    xsane.preview_gamma_data_blue  = 0;
  }

#ifdef HAVE_ANY_GIMP
  if (xsane.mode == XSANE_GIMP_EXTENSION)
  {
    gimp_quit();
  }
#endif

  if (preferences.printer)
  {
    free(preferences.printer);
  }

  exit(0);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_exit(void) /* this is called when xsane exits before gtk_main is called */
{
  DBG(DBG_proc, "xsane_exit\n");

  while (xsane.back_gtk_message_dialog_active)
  {
    DBG(DBG_info, "calling gtk_main_iteration\n");
    gtk_main_iteration();
  }

  sane_exit();

#ifdef HAVE_ANY_GIMP
  if (xsane.mode == XSANE_GIMP_EXTENSION)
  {
    gimp_quit();
  }
#endif
  exit(0);
}


/* ---------------------------------------------------------------------------------------------------------------------- */

/* Invoked when window manager's "delete" (or "close") function is invoked.  */
gint xsane_scan_win_delete(GtkWidget *w, gpointer data)
{
 int unsaved_images = 0;
 Viewer *viewer = xsane.viewer_list;

  DBG(DBG_proc, "xsane_scan_win_delete\n");

  xsane_scan_done((SANE_Status)-1); /* stop scanner when still scanning */

  while (viewer)
  {
    if (!viewer->image_saved)
    {
      unsaved_images++;
    }
    viewer = viewer->next_viewer;
  }

  if (unsaved_images)
  {
   char buf[TEXTBUFSIZE];

    snprintf(buf, sizeof(buf), WARN_UNSAVED_IMAGES, unsaved_images);
    if (!xsane_back_gtk_decision(ERR_HEADER_WARNING, (gchar **) warning_xpm, buf, BUTTON_DO_NOT_CLOSE, BUTTON_DISCARD_ALL_IMAGES, TRUE /* wait */) == FALSE)
    {
      return TRUE;
    }
  }

  xsane_pref_save();
  xsane_pref_save_media();

  if (preferences.save_devprefs_at_exit)
  {
    xsane_device_preferences_store();
  }

  xsane_quit();
  return FALSE;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

GtkWidget *xsane_files_build_menu(void)
{
 GtkWidget *menu, *item;

  DBG(DBG_proc, "xsane_files_build_menu\n");

  menu = gtk_menu_new();
  gtk_menu_set_accel_group(GTK_MENU(menu), xsane.accelerator_group);

  /* XSane info dialog */

  item = gtk_menu_item_new_with_label(MENU_ITEM_INFO);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_I, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_info_dialog, NULL);
  gtk_widget_show(item);

  
  /* Quit */

  item = gtk_menu_item_new_with_label(MENU_ITEM_QUIT);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_Q, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_container_add(GTK_CONTAINER(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate",  (GtkSignalFunc) xsane_scan_win_delete, NULL);
  gtk_widget_show(item);

  return menu;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

gint xsane_medium_move_up_callback(GtkWidget *widget, GtkWidget *medium_widget)
{
 int selection;
  
  DBG(DBG_proc, "xsane_medium_move_up_callback\n");
  
  selection = (long) gtk_object_get_data(GTK_OBJECT(medium_widget), "Selection");

  if (selection > 1) /* make sure "full range" stays at top */
  {
   Preferences_medium_t *help_medium;
   
    DBG(DBG_info ,"moving up %s\n", preferences.medium[selection]->name);
  
    help_medium = preferences.medium[selection-1];
    preferences.medium[selection-1] = preferences.medium[selection];
    preferences.medium[selection] = help_medium;
    
    if (preferences.medium_nr == selection)
    {
      preferences.medium_nr--;
    }
    else if (preferences.medium_nr == selection-1)
    {
      preferences.medium_nr++;
    }
  
    xsane_back_gtk_refresh_dialog(); /* brute-force: update menu */
  }

  return TRUE; /* event is handled */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

gint xsane_medium_move_down_callback(GtkWidget *widget, GtkWidget *medium_widget)
{
 int selection;
  
  DBG(DBG_proc, "xsane_medium_move_up_callback\n");
  
  selection = (long) gtk_object_get_data(GTK_OBJECT(medium_widget), "Selection");

  if ((selection) && (selection < preferences.medium_definitions-1))
  {
   Preferences_medium_t *help_medium;
   
    DBG(DBG_info ,"moving down %s\n", preferences.medium[selection]->name);
  
    help_medium = preferences.medium[selection];
    preferences.medium[selection] = preferences.medium[selection+1];
    preferences.medium[selection+1] = help_medium;
    
    if (preferences.medium_nr == selection)
    {
      preferences.medium_nr++;
    }
    else if (preferences.medium_nr == selection+1)
    {
      preferences.medium_nr--;
    }
  
    xsane_back_gtk_refresh_dialog(); /* brute-force: update menu */
  }

  return TRUE; /* event is handled */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

gint xsane_medium_rename_callback(GtkWidget *widget, GtkWidget *medium_widget)
{
 int selection;
 char *oldname;
 char *newname;
 GtkWidget *old_medium_menu;
 int old_selection;

  DBG(DBG_proc, "xsane_medium_rename_callback\n");

  selection = (long) gtk_object_get_data(GTK_OBJECT(medium_widget), "Selection");

  oldname = strdup(preferences.medium[selection]->name);

  DBG(DBG_info ,"rename %s\n", oldname);

  /* set menu in correct state, is a bit strange this way but I do not have a better idea */
  old_medium_menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(xsane.medium_widget));
  old_selection = (long) gtk_object_get_data(GTK_OBJECT(gtk_menu_get_active(GTK_MENU(old_medium_menu))), "Selection");
  gtk_menu_popdown(GTK_MENU(old_medium_menu));
  gtk_option_menu_set_history(GTK_OPTION_MENU(xsane.medium_widget), old_selection);

  if (!xsane_front_gtk_getname_dialog(WINDOW_MEDIUM_RENAME, DESC_MEDIUM_RENAME, oldname, &newname))
  {
    free((void *)preferences.medium[selection]->name);
    preferences.medium[selection]->name = strdup(newname);
    DBG(DBG_info, "renaming %s to %s\n", oldname, newname);

    xsane_back_gtk_refresh_dialog(); /* update menu */
  }

  free(oldname);
  free(newname);

  xsane_set_sensitivity(TRUE);

  return TRUE; /* event is handled */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

gint xsane_medium_add_callback(GtkWidget *widget, GtkWidget *medium_widget)
{
 int selection;
 char *oldname;
 char *newname;
 GtkWidget *old_medium_menu;
 int old_selection;
 int i;

  DBG(DBG_proc, "xsane_medium_add_callback\n");

  /* add new item after selected item */
  selection = 1 + (long) gtk_object_get_data(GTK_OBJECT(medium_widget), "Selection");

  oldname = strdup(TEXT_NEW_MEDIA_NAME);

  /* set menu in correct state, is a bit strange this way but I do not have a better idea */
  old_medium_menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(xsane.medium_widget));
  old_selection = (long) gtk_object_get_data(GTK_OBJECT(gtk_menu_get_active(GTK_MENU(old_medium_menu))), "Selection");
  gtk_menu_popdown(GTK_MENU(old_medium_menu));
  gtk_option_menu_set_history(GTK_OPTION_MENU(xsane.medium_widget), old_selection);

  if (!xsane_front_gtk_getname_dialog(WINDOW_MEDIUM_ADD, DESC_MEDIUM_ADD, oldname, &newname))
  {
    preferences.medium_definitions++;
    preferences.medium = (Preferences_medium_t**)realloc(preferences.medium, preferences.medium_definitions * sizeof(void *));

    for (i = preferences.medium_definitions-1; i > selection; i--)
    {
      preferences.medium[i] = preferences.medium[i-1];
    }

    DBG(DBG_info, "adding new media %s\n", newname);

    if (xsane.negative)
    {
      preferences.medium[selection] = (Preferences_medium_t*)calloc(sizeof(Preferences_medium_t), 1);
      preferences.medium[selection]->name            = strdup(newname);
      preferences.medium[selection]->shadow_gray     = 100.0 - xsane.slider_gray.value[2];
      preferences.medium[selection]->shadow_red      = 100.0 - xsane.slider_red.value[2];
      preferences.medium[selection]->shadow_green    = 100.0 - xsane.slider_green.value[2];
      preferences.medium[selection]->shadow_blue     = 100.0 - xsane.slider_blue.value[2];
      preferences.medium[selection]->highlight_gray  = 100.0 - xsane.slider_gray.value[0];
      preferences.medium[selection]->highlight_red   = 100.0 - xsane.slider_red.value[0];
      preferences.medium[selection]->highlight_green = 100.0 - xsane.slider_green.value[0];
      preferences.medium[selection]->highlight_blue  = 100.0 - xsane.slider_blue.value[0];
      preferences.medium[selection]->gamma_gray      = xsane.gamma;
      preferences.medium[selection]->gamma_red       = xsane.gamma * xsane.gamma_red;
      preferences.medium[selection]->gamma_green     = xsane.gamma * xsane.gamma_green;
      preferences.medium[selection]->gamma_blue      = xsane.gamma * xsane.gamma_blue;
      preferences.medium[selection]->negative        = xsane.negative;
    }
    else
    { 
      preferences.medium[selection] = (Preferences_medium_t*)calloc(sizeof(Preferences_medium_t), 1);
      preferences.medium[selection]->name            = strdup(newname);
      preferences.medium[selection]->shadow_gray     = xsane.slider_gray.value[0];
      preferences.medium[selection]->shadow_red      = xsane.slider_red.value[0];
      preferences.medium[selection]->shadow_green    = xsane.slider_green.value[0];
      preferences.medium[selection]->shadow_blue     = xsane.slider_blue.value[0];
      preferences.medium[selection]->highlight_gray  = xsane.slider_gray.value[2];
      preferences.medium[selection]->highlight_red   = xsane.slider_red.value[2];
      preferences.medium[selection]->highlight_green = xsane.slider_green.value[2];
      preferences.medium[selection]->highlight_blue  = xsane.slider_blue.value[2];
      preferences.medium[selection]->gamma_gray      = xsane.gamma;
      preferences.medium[selection]->gamma_red       = xsane.gamma * xsane.gamma_red;
      preferences.medium[selection]->gamma_green     = xsane.gamma * xsane.gamma_green;
      preferences.medium[selection]->gamma_blue      = xsane.gamma * xsane.gamma_blue;
      preferences.medium[selection]->negative        = xsane.negative;
    }

    preferences.medium_nr = selection; /* activate new created medium */

    xsane_back_gtk_refresh_dialog(); /* update menu */
    xsane_enhancement_by_gamma(); /* update sliders */
  }

  free(oldname);
  free(newname);

  xsane_set_sensitivity(TRUE);

  return TRUE; /* event is handled */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

gint xsane_medium_delete_callback(GtkWidget *widget, GtkWidget *medium_widget)
{
 int selection, i;

  DBG(DBG_proc, "xsane_medium_delete_callback\n");

  selection = (long) gtk_object_get_data(GTK_OBJECT(medium_widget), "Selection");

  if (selection) /* full range can not be deleted */
  {
    DBG(DBG_info ,"deleting %s\n", preferences.medium[selection]->name);

  //  free(preferences.medium[selection]);

    preferences.medium_definitions--;

    for (i = selection; i < preferences.medium_definitions; i++)
    {
      preferences.medium[i] = preferences.medium[i+1];
    }
    preferences.medium = (Preferences_medium_t**)realloc(preferences.medium, preferences.medium_definitions * sizeof(void *)); // NEW

    if (preferences.medium_nr == selection)
    {
      preferences.medium_nr--;
      xsane.medium_changed = TRUE;

      if (xsane.medium_calibration) /* are we running in medium calibration mode? */
      {
        xsane_apply_medium_definition_as_enhancement(preferences.medium[preferences.medium_nr]);
        xsane_set_medium(NULL);
      }
      else
      {
        xsane_set_medium(preferences.medium[preferences.medium_nr]);
      }

      xsane_enhancement_by_gamma(); /* update sliders */
    }

    xsane_back_gtk_refresh_dialog(); /* update menu */

    xsane_update_gamma_curve(TRUE); /* if necessary update preview gamma */

    preview_display_valid(xsane.preview); /* update valid status of preview image */
    /* the valid status depends on gamma handling an medium change */
  }

  return TRUE; /* event is handled */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

gint xsane_medium_context_menu_callback(GtkWidget *widget, GdkEvent *event)
{
 GtkWidget *menu;
 GtkWidget *menu_item;
 GdkEventButton *event_button;
 int selection;
                                                                                                
  DBG(DBG_proc, "xsane_medium_context_menu_callback\n");
                                                                                                
  selection = (long) gtk_object_get_data(GTK_OBJECT(widget), "Selection");
                                                                                                
  if (event->type == GDK_BUTTON_PRESS)
  {
    event_button = (GdkEventButton *) event;
                                                                                                
    if (event_button->button == 3)
    {
     char buf[TEXTBUFSIZE];

      menu = gtk_menu_new();

      if (xsane.medium_calibration) /* are we running in medium calibration mode? */
      {
        /* add medium */
        menu_item = gtk_menu_item_new_with_label(MENU_ITEM_MEDIUM_ADD);
        gtk_widget_show(menu_item);
        gtk_container_add(GTK_CONTAINER(menu), menu_item);
        g_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) xsane_medium_add_callback, widget);
      }
                                                                                                
      /* rename medium */
      snprintf(buf, sizeof(buf), "%s: %s", preferences.medium[selection]->name, MENU_ITEM_RENAME);
      menu_item = gtk_menu_item_new_with_label(buf);
      gtk_widget_show(menu_item);
      gtk_container_add(GTK_CONTAINER(menu), menu_item);
      g_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) xsane_medium_rename_callback, widget);
                                                                                                
      if (selection) /* not available for "full area" */
      {
        /* delete medium */
        snprintf(buf, sizeof(buf), "%s: %s", preferences.medium[selection]->name, MENU_ITEM_DELETE);
        menu_item = gtk_menu_item_new_with_label(buf);
        gtk_widget_show(menu_item);
        gtk_container_add(GTK_CONTAINER(menu), menu_item);
        g_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) xsane_medium_delete_callback, widget);
      }
                                                                                                
      if (selection>1) /* available from 3rd item */
      {
        /* move up */
        snprintf(buf, sizeof(buf), "%s: %s", preferences.medium[selection]->name, MENU_ITEM_MOVE_UP);
        menu_item = gtk_menu_item_new_with_label(buf);
        gtk_widget_show(menu_item);
        gtk_container_add(GTK_CONTAINER(menu), menu_item);
        g_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) xsane_medium_move_up_callback, widget);
      }
                                                                                                
      if ((selection) && (selection < preferences.medium_definitions-1))
      {
        /* move down */
        snprintf(buf, sizeof(buf), "%s: %s", preferences.medium[selection]->name, MENU_ITEM_MOVE_DWN);
        menu_item = gtk_menu_item_new_with_label(buf);
        gtk_widget_show(menu_item);
        gtk_container_add(GTK_CONTAINER(menu), menu_item);
        g_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) xsane_medium_move_down_callback, widget);
      }
                                                                                                
      gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event_button->button, event_button->time);
                                                                                                
     return TRUE; /* event is handled */
    }
  }
                                                                                                
 return FALSE;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_set_medium_callback(GtkWidget *widget, gpointer data)
{
 int medium_nr = (long) data;

  if (medium_nr != preferences.medium_nr)
  {
    xsane.medium_changed = TRUE;
  }

  preferences.medium_nr = medium_nr;

  if (xsane.medium_calibration) /* are we running in medium calibration mode? */
  {
    xsane_apply_medium_definition_as_enhancement(preferences.medium[preferences.medium_nr]);
    xsane_set_medium(NULL);
  }
  else
  {
    xsane_set_medium(preferences.medium[preferences.medium_nr]);
  }

  xsane_enhancement_by_gamma(); /* update sliders */
  xsane_back_gtk_refresh_dialog(); /* update menu */

  xsane_update_gamma_curve(TRUE); /* if necessary update preview gamma */

  preview_display_valid(xsane.preview); /* update valid status of preview image */
  /* the valid status depends on gamma handling an medium change */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_set_pref_unit_callback(GtkWidget *widget, gpointer data)
{
 const char *unit = (const char *)data;
 double unit_conversion_factor = 1.0;

  DBG(DBG_proc, "xsane_set_pref_unit_callback\n");

  g_signal_handlers_block_by_func(GTK_OBJECT(xsane.length_unit_mm), (void *) xsane_set_pref_unit_callback, (void *)"mm");
  g_signal_handlers_block_by_func(GTK_OBJECT(xsane.length_unit_cm), (void *) xsane_set_pref_unit_callback, (void *)"cm");
  g_signal_handlers_block_by_func(GTK_OBJECT(xsane.length_unit_in), (void *) xsane_set_pref_unit_callback, (void *)"in");

  if (strcmp(unit, "mm") == 0)
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.length_unit_mm), TRUE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.length_unit_cm), FALSE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.length_unit_in), FALSE);
  }
  else if (strcmp(unit, "cm") == 0)
  {
    unit_conversion_factor = 10.0;
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.length_unit_mm), FALSE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.length_unit_cm), TRUE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.length_unit_in), FALSE);
  }
  else if (strcmp(unit, "in") == 0)
  {
    unit_conversion_factor = 25.4;
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.length_unit_mm), FALSE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.length_unit_cm), FALSE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.length_unit_in), TRUE);
  }

  g_signal_handlers_unblock_by_func(GTK_OBJECT(xsane.length_unit_mm), (void *) xsane_set_pref_unit_callback, (void *)"mm");
  g_signal_handlers_unblock_by_func(GTK_OBJECT(xsane.length_unit_cm), (void *) xsane_set_pref_unit_callback, (void *)"cm");
  g_signal_handlers_unblock_by_func(GTK_OBJECT(xsane.length_unit_in), (void *) xsane_set_pref_unit_callback, (void *)"in");

  preferences.length_unit = unit_conversion_factor;

  xsane_refresh_dialog();

  if (xsane.preview)
  {
    preview_area_resize(xsane.preview); /* redraw rulers */
  }

  xsane_batch_scan_update_label_list(); /* update units in batch scan list */

  xsane_pref_save();
}

/* ---------------------------------------------------------------------------------------------------------------------- */
#ifdef HAVE_LIBLCMS
void xsane_enable_color_management_callback(GtkWidget *widget, gpointer data)
{
  DBG(DBG_proc, "xsane_enable_color_management_callback\n");

  xsane.enable_color_management = (GTK_CHECK_MENU_ITEM(widget)->active);

  gtk_widget_set_sensitive(xsane.edit_medium_definition_widget, (xsane.enable_color_management) == 0);

  if (xsane.enable_color_management)
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.edit_medium_definition_widget), FALSE);
    xsane.medium_calibration = FALSE;
  }

  xsane.gamma            = 1.0;
  xsane.gamma_red        = 1.0;
  xsane.gamma_green      = 1.0;
  xsane.gamma_blue       = 1.0;
  xsane.brightness       = 0.0;
  xsane.brightness_red   = 0.0;
  xsane.brightness_green = 0.0;
  xsane.brightness_blue  = 0.0;
  xsane.contrast         = 0.0;
  xsane.contrast_red     = 0.0;
  xsane.contrast_green   = 0.0;
  xsane.contrast_blue    = 0.0;
  xsane.threshold        = 50.0;

  xsane.slider_gray.value[2]  = 100.0;
  xsane.slider_gray.value[1]  = 50.0;
  xsane.slider_gray.value[0]  = 0.0;
  xsane.slider_red.value[2]   = 100.0;
  xsane.slider_red.value[1]   = 50.0;
  xsane.slider_red.value[0]   = 0.0;
  xsane.slider_green.value[2] = 100.0;
  xsane.slider_green.value[1] = 50.0;
  xsane.slider_green.value[0] = 0.0;
  xsane.slider_blue.value[2]  = 100.0;
  xsane.slider_blue.value[1]  = 50.0;
  xsane.slider_blue.value[0]  = 0.0;

  xsane_update_sliders();
  xsane_update_gamma_curve(FALSE);
  xsane_batch_scan_update_icon_list();
  xsane_refresh_dialog();
}
#endif
/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_edit_medium_definition_callback(GtkWidget *widget, gpointer data)
{
  DBG(DBG_proc, "xsane_edit_medium_definition_callback\n");

  if (GTK_CHECK_MENU_ITEM(widget)->active) /* enable edit mode */
  {
    DBG(DBG_info2, "enabling edit mode\n");

    xsane.medium_calibration = TRUE;
    xsane.no_preview_medium_gamma = TRUE;

    xsane.brightness_min    = XSANE_MEDIUM_CALIB_BRIGHTNESS_MIN;
    xsane.brightness_max    = XSANE_MEDIUM_CALIB_BRIGHTNESS_MAX;
    xsane.contrast_gray_min = XSANE_CONTRAST_GRAY_MIN;
    xsane.contrast_min      = XSANE_MEDIUM_CALIB_CONTRAST_MIN;
    xsane.contrast_max      = XSANE_MEDIUM_CALIB_CONTRAST_MAX;

    xsane_apply_medium_definition_as_enhancement(preferences.medium[preferences.medium_nr]);
    xsane_set_medium(NULL);
  }
  else  /* disable edit mode */
  {
    DBG(DBG_info2, "disabling edit mode\n");

    xsane.medium_calibration = FALSE;
    xsane.no_preview_medium_gamma = FALSE;

    xsane.brightness_min    = XSANE_BRIGHTNESS_MIN;
    xsane.brightness_max    = XSANE_BRIGHTNESS_MAX;
    xsane.contrast_gray_min = XSANE_CONTRAST_GRAY_MIN;
    xsane.contrast_min      = XSANE_CONTRAST_MIN;
    xsane.contrast_max      = XSANE_CONTRAST_MAX;

    xsane_apply_medium_definition_as_enhancement(preferences.medium[0]);
    xsane_set_medium(preferences.medium[preferences.medium_nr]);
  }

  xsane_enhancement_by_gamma(); /* update sliders */
  xsane_back_gtk_refresh_dialog(); /* update dialog */

  xsane_update_gamma_curve(TRUE); /* if necessary update preview gamma */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_set_update_policy_callback(GtkWidget *widget, gpointer data)
{
 GtkUpdateType policy = (GtkUpdateType)(long) data;

  DBG(DBG_proc, "xsane_set_update_policy_callback\n");

  g_signal_handlers_block_by_func(GTK_OBJECT(xsane.update_policy_continu), (void *) xsane_set_update_policy_callback,
                                   (void *) GTK_UPDATE_CONTINUOUS);
  g_signal_handlers_block_by_func(GTK_OBJECT(xsane.update_policy_discont), (void *) xsane_set_update_policy_callback,
                                   (void *) GTK_UPDATE_DISCONTINUOUS);
  g_signal_handlers_block_by_func(GTK_OBJECT(xsane.update_policy_delayed), (void *) xsane_set_update_policy_callback,
                                   (void *) GTK_UPDATE_DELAYED);

  if (policy == GTK_UPDATE_CONTINUOUS)
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.update_policy_continu), TRUE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.update_policy_discont), FALSE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.update_policy_delayed), FALSE);
  }
  else if (policy == GTK_UPDATE_DISCONTINUOUS)
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.update_policy_continu), FALSE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.update_policy_discont), TRUE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.update_policy_delayed), FALSE);
  }
  else if (policy == GTK_UPDATE_DELAYED)
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.update_policy_continu), FALSE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.update_policy_discont), FALSE);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.update_policy_delayed), TRUE);
  }

  g_signal_handlers_unblock_by_func(GTK_OBJECT(xsane.update_policy_continu), (void *) xsane_set_update_policy_callback,
                                     (void *) GTK_UPDATE_CONTINUOUS);
  g_signal_handlers_unblock_by_func(GTK_OBJECT(xsane.update_policy_discont), (void *) xsane_set_update_policy_callback,
                                     (void *) GTK_UPDATE_DISCONTINUOUS);
  g_signal_handlers_unblock_by_func(GTK_OBJECT(xsane.update_policy_delayed), (void *) xsane_set_update_policy_callback,
                                     (void *) GTK_UPDATE_DELAYED);

  preferences.gtk_update_policy = policy;
  xsane_pref_save();

  xsane_back_gtk_refresh_dialog();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

gint xsane_close_info_callback(GtkWidget *widget, gpointer data)
{
 GtkWidget *dialog_widget = (GtkWidget *)data;

  DBG(DBG_proc, "xsane_close_info_callback\n");

  gtk_widget_destroy(dialog_widget);

  xsane_set_sensitivity(TRUE);

 return FALSE;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_info_dialog(GtkWidget *widget, gpointer data)
{
 GtkWidget *info_dialog, *vbox, *button, *label, *frame, *framebox, *hbox, *table;
 char buf[TEXTBUFSIZE];
 char *bufptr;
 GtkAccelGroup *accelerator_group;

  DBG(DBG_proc, "xsane_info_dialog\n");

  sane_get_parameters(xsane.device_options.dev, &xsane.param); /* update xsane.param */

  info_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(info_dialog), GTK_WIN_POS_CENTER);
  gtk_window_set_resizable(GTK_WINDOW(info_dialog), FALSE);
  g_signal_connect(GTK_OBJECT(info_dialog), "destroy", GTK_SIGNAL_FUNC(xsane_close_info_callback), info_dialog);
  snprintf(buf, sizeof(buf), "%s %s %s", xsane.prog_name, WINDOW_INFO, xsane.device_text);
  gtk_window_set_title(GTK_WINDOW(info_dialog), buf);
  gtk_container_set_border_width(GTK_CONTAINER(info_dialog), 5);

  accelerator_group = gtk_accel_group_new();
  gtk_window_add_accel_group(GTK_WINDOW(info_dialog), accelerator_group); 

  xsane_set_window_icon(info_dialog, 0);

  vbox = gtk_vbox_new(/* not homogeneous */ FALSE, 5);
  gtk_container_add(GTK_CONTAINER(info_dialog), vbox);
  gtk_widget_show(vbox);

  frame = gtk_frame_new(TEXT_SCANNER_BACKEND);
  gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
  gtk_widget_show(frame);

  framebox = gtk_vbox_new(/* not homogeneous */ FALSE, 0);
  gtk_container_add(GTK_CONTAINER(frame), framebox);
  gtk_widget_show(framebox);

  hbox = gtk_hbox_new(/* homogeneous */ FALSE, 0);
  gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, TRUE, 5);
  gtk_widget_show(hbox);

  table = gtk_table_new(6, 2, FALSE);
  gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, 5);
  gtk_widget_show(table);

  snprintf(buf, sizeof(buf), "%s", TEXT_VENDOR);
  label = xsane_info_table_text_new(table, buf, 0, 0);
  snprintf(buf, sizeof(buf), "%s", xsane.devlist[xsane.selected_dev]->vendor);
  label = xsane_info_table_text_new(table, buf, 1, 0);

  snprintf(buf, sizeof(buf), "%s", TEXT_MODEL);
  label = xsane_info_table_text_new(table, buf, 0, 1);
  snprintf(buf, sizeof(buf), "%s", xsane.devlist[xsane.selected_dev]->model);
  label = xsane_info_table_text_new(table, buf, 1, 1);

  snprintf(buf, sizeof(buf), "%s", TEXT_TYPE);
  label = xsane_info_table_text_new(table, buf, 0, 2);
  snprintf(buf, sizeof(buf), "%s", _(xsane.devlist[xsane.selected_dev]->type));
  label = xsane_info_table_text_new(table, buf, 1, 2);

  snprintf(buf, sizeof(buf), "%s", TEXT_DEVICE);
  label = xsane_info_table_text_new(table, buf, 0, 3);
  bufptr = strrchr(xsane.devlist[xsane.selected_dev]->name, ':');
  if (bufptr)
  {
    snprintf(buf, sizeof(buf), "%s", bufptr+1);
  }
  else
  {
    snprintf(buf, sizeof(buf), "%s", xsane.devlist[xsane.selected_dev]->name);
  }
  label = xsane_info_table_text_new(table, buf, 1, 3);

  snprintf(buf, sizeof(buf), "%s", xsane.devlist[xsane.selected_dev]->name);
  bufptr = strrchr(buf, ':');
  if (bufptr)
  {
    *bufptr = 0; 
    label = xsane_info_table_text_new(table, buf, 1, 4);
    snprintf(buf, sizeof(buf), "%s", TEXT_LOADED_BACKEND);
    label = xsane_info_table_text_new(table, buf, 0, 4);
  }

  snprintf(buf, sizeof(buf), "%s", TEXT_SANE_VERSION);
  label = xsane_info_table_text_new(table, buf, 0, 5);
  snprintf(buf, sizeof(buf), "%d.%d.%d",SANE_VERSION_MAJOR(xsane.sane_backend_versioncode),
                                SANE_VERSION_MINOR(xsane.sane_backend_versioncode),
                                SANE_VERSION_BUILD(xsane.sane_backend_versioncode));
  label = xsane_info_table_text_new(table, buf, 1, 5);


  frame = gtk_frame_new(TEXT_RECENT_VALUES);
  gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
  gtk_widget_show(frame);

  framebox = gtk_vbox_new(/* not homogeneous */ FALSE, 0);
  gtk_container_add(GTK_CONTAINER(frame), framebox);
  gtk_widget_show(framebox);

  hbox = gtk_hbox_new(/* homogeneous */ FALSE, 0);
  gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, TRUE, 5);
  gtk_widget_show(hbox);

  table = gtk_table_new(4, 2, FALSE);
  gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, 5);
  gtk_widget_show(table);

  if ((xsane.xsane_channels > 1) && (xsane.scanner_gamma_color)) /* color gamma correction by scanner */
  {
   const SANE_Option_Descriptor *opt;

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_CORR_BY);
    label = xsane_info_table_text_new(table, buf, 0, 0);
    snprintf(buf, sizeof(buf), "%s", TEXT_SCANNER);
    label = xsane_info_table_text_new(table, buf, 1, 0);

    opt = xsane_get_option_descriptor(xsane.device_options.dev, xsane.device_options.well_known.gamma_vector_r);

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_INPUT_DEPTH);
    label = xsane_info_table_text_new(table, buf, 0, 1);
    snprintf(buf, sizeof(buf), "%d bit", (int) (0.5 + log((double)opt->size / sizeof(opt->type)) / log(2.0)));
    label = xsane_info_table_text_new(table, buf, 1, 1);

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_OUTPUT_DEPTH);
    label = xsane_info_table_text_new(table, buf, 0, 2);
    snprintf(buf, sizeof(buf), "%d bit", (int) (0.5 + log(opt->constraint.range->max+1.0) / log(2.0)));
    label = xsane_info_table_text_new(table, buf, 1, 2);
  }
  else if ((!xsane.xsane_channels > 1) && (xsane.scanner_gamma_gray)) /* gray gamma correction by scanner */
  {
   const SANE_Option_Descriptor *opt;

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_CORR_BY);
    label = xsane_info_table_text_new(table, buf, 0, 0);
    snprintf(buf, sizeof(buf), "%s", TEXT_SCANNER);
    label = xsane_info_table_text_new(table, buf, 1, 0);
    
    opt = xsane_get_option_descriptor(xsane.device_options.dev, xsane.device_options.well_known.gamma_vector);

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_INPUT_DEPTH);
    label = xsane_info_table_text_new(table, buf, 0, 1);
    snprintf(buf, sizeof(buf), "%d bit", (int) (0.5 + log((double)opt->size / sizeof(opt->type)) / log(2.0)));
    label = xsane_info_table_text_new(table, buf, 1, 1);

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_OUTPUT_DEPTH);
    label = xsane_info_table_text_new(table, buf, 0, 2);
    snprintf(buf, sizeof(buf), "%d bit", (int) (0.5 + log(opt->constraint.range->max+1.0) / log(2.0)));
    label = xsane_info_table_text_new(table, buf, 1, 2);
  }
  else if (xsane.param.depth != 1) /* gamma correction by xsane */
  {
    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_CORR_BY);
    label = xsane_info_table_text_new(table, buf, 0, 0);
    snprintf(buf, sizeof(buf), "%s", TEXT_SOFTWARE_XSANE);
    label = xsane_info_table_text_new(table, buf, 1, 0);

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_INPUT_DEPTH);
    label = xsane_info_table_text_new(table, buf, 0, 1);
    snprintf(buf, sizeof(buf), "%d bit", xsane.param.depth);
    label = xsane_info_table_text_new(table, buf, 1, 1);

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_OUTPUT_DEPTH);
    label = xsane_info_table_text_new(table, buf, 0, 2);
/*    snprintf(buf, sizeof(buf), "%d bit", 8); */
    snprintf(buf, sizeof(buf), "%d bit", xsane.param.depth); 
    label = xsane_info_table_text_new(table, buf, 1, 2);
  }
  else /* no gamma enhancement */
  {
    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_CORR_BY);
    label = xsane_info_table_text_new(table, buf, 0, 0);
    snprintf(buf, sizeof(buf), "%s", TEXT_NONE);
    label = xsane_info_table_text_new(table, buf, 1, 0);

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_INPUT_DEPTH);
    label = xsane_info_table_text_new(table, buf, 0, 1);
    snprintf(buf, sizeof(buf), "%s", TEXT_NONE);
    label = xsane_info_table_text_new(table, buf, 1, 1);

    snprintf(buf, sizeof(buf), "%s", TEXT_GAMMA_OUTPUT_DEPTH);
    label = xsane_info_table_text_new(table, buf, 0, 2);
    snprintf(buf, sizeof(buf), "%s", TEXT_NONE);
    label = xsane_info_table_text_new(table, buf, 1, 2);
  }

  snprintf(buf, sizeof(buf), "%s", TEXT_SCANNER_OUTPUT_DEPTH);
  label = xsane_info_table_text_new(table, buf, 0, 3);
  snprintf(buf, sizeof(buf), "%d bit", xsane.param.depth);
  label = xsane_info_table_text_new(table, buf, 1, 3);

  frame = gtk_frame_new(TEXT_OUTPUT_FORMATS);
  gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
  gtk_widget_show(frame);

  framebox = gtk_vbox_new(/* not homogeneous */ FALSE, 0);
  gtk_container_add(GTK_CONTAINER(frame), framebox);
  gtk_widget_show(framebox);

  hbox = gtk_hbox_new(/* homogeneous */ FALSE, 0);
  gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, TRUE, 5);
  gtk_widget_show(hbox);

  table = gtk_table_new(2, 2, FALSE);
  gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, 5);
  gtk_widget_show(table);

  snprintf(buf, sizeof(buf), "%s", TEXT_8BIT_FORMATS);
  label = xsane_info_table_text_new(table, buf, 0, 0);

  bufptr=buf;

#ifdef HAVE_LIBJPEG
  sprintf(bufptr, "JPEG, ");
  bufptr += strlen(bufptr);
#endif

  sprintf(bufptr, "PDF, ");
  bufptr += strlen(bufptr);

#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
  sprintf(bufptr, "PNG, ");
  bufptr += strlen(bufptr);
#endif
#endif

  sprintf(bufptr, "PNM, ");
  bufptr += strlen(bufptr);

  sprintf(bufptr, "PS, ");
  bufptr += strlen(bufptr);

#ifdef SUPPORT_RGBA
  sprintf(bufptr, "RGBA, ");
  bufptr += strlen(bufptr);
#endif

#ifdef HAVE_LIBTIFF
  sprintf(bufptr, "TIFF, ");
  bufptr += strlen(bufptr);
#endif

  sprintf(bufptr, "TXT, ");
  bufptr += strlen(bufptr);

  bufptr--;
  bufptr--;
  *bufptr = 0; /* erase last comma */

  label = xsane_info_table_text_new(table, buf, 1, 0);

  snprintf(buf, sizeof(buf), "%s", TEXT_16BIT_FORMATS);
  label = xsane_info_table_text_new(table, buf, 0, 1);

  bufptr=buf;

#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
  sprintf(bufptr, "PNG, ");
  bufptr += strlen(bufptr);
#endif
#endif

  sprintf(bufptr, "PNM, ");
  bufptr += strlen(bufptr);

#ifdef SUPPORT_RGBA
  sprintf(bufptr, "RGBA, ");
  bufptr += strlen(bufptr);
#endif

#ifdef HAVE_LIBTIFF
  sprintf(bufptr, "TIFF, ");
  bufptr += strlen(bufptr);
#endif

  bufptr--;
  bufptr--;
  *bufptr = 0; /* erase last comma */

  label = xsane_info_table_text_new(table, buf, 1, 1);

/*  gtk_label_set((GtkLabel *)label, "HALLO"); */

#ifdef HAVE_GTK2
  button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
#else
  button = gtk_button_new_with_label(BUTTON_CLOSE);
#endif
  gtk_widget_add_accelerator(button, "clicked", accelerator_group, GDK_Escape, (GdkModifierType)0, DEF_GTK_ACCEL_LOCKED);  
  GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_close_info_callback, info_dialog);
  gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
  gtk_widget_grab_default(button);
  gtk_widget_show(button);

  gtk_widget_show(info_dialog);

  xsane_set_sensitivity(FALSE);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static GtkWidget *about_dialog = NULL;

static int xsane_close_about_dialog_callback(GtkWidget *widget, gpointer data)
{
  DBG(DBG_proc, "xsane_close_about_dialog_callback\n");

  gtk_widget_destroy(about_dialog);
  about_dialog = NULL;

 return FALSE;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_about_dialog(GtkWidget *widget, gpointer data)
{
 GtkWidget *vbox, *hbox, *button, *label;
 char buf[1024];
 char filename[PATH_MAX];
 GtkWidget *pixmapwidget;
 GdkBitmap *mask;
 GdkPixmap *pixmap;
 GtkStyle *style;
 GdkColor *bg_trans;
 GtkAccelGroup *accelerator_group;

  DBG(DBG_proc, "xsane_about_dialog\n");

  if (about_dialog)
  {
    return;
  }

  about_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(about_dialog), GTK_WIN_POS_CENTER);
  gtk_window_set_resizable(GTK_WINDOW(about_dialog), FALSE);
  g_signal_connect(GTK_OBJECT(about_dialog), "destroy", GTK_SIGNAL_FUNC(xsane_close_about_dialog_callback), NULL);
  snprintf(buf, sizeof(buf), "%s %s", WINDOW_ABOUT_XSANE, xsane.prog_name);
  gtk_window_set_title(GTK_WINDOW(about_dialog), buf);
  gtk_container_set_border_width(GTK_CONTAINER(about_dialog), 5);

  accelerator_group = gtk_accel_group_new();
  gtk_window_add_accel_group(GTK_WINDOW(about_dialog), accelerator_group); 

  xsane_set_window_icon(about_dialog, 0);

  hbox = gtk_hbox_new(/* not homogeneous */ TRUE, 5);
  gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
  gtk_container_add(GTK_CONTAINER(about_dialog), hbox);
  gtk_widget_show(hbox);

  vbox = gtk_vbox_new(/* not homogeneous */ FALSE, 5);
  gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 20);
  gtk_widget_show(vbox);

  /* xsane logo */
  gtk_widget_realize(about_dialog);

  style = gtk_widget_get_style(about_dialog);
  bg_trans = &style->bg[GTK_STATE_NORMAL];

  xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-logo", 0, ".xpm", XSANE_PATH_SYSTEM);
  pixmap = gdk_pixmap_create_from_xpm(about_dialog->window, &mask, bg_trans, filename);
  pixmapwidget = gtk_image_new_from_pixmap(pixmap, mask);
  gtk_box_pack_start(GTK_BOX(vbox), pixmapwidget, FALSE, FALSE, 2);
  gtk_widget_show(pixmapwidget);
  gdk_drawable_unref(pixmap);
  gdk_drawable_unref(mask);


  xsane_separator_new(vbox, 5);


  snprintf(buf, sizeof(buf), "XSane %s %s\n"
                             "%s %s\n"
                             "\n"
                             "%s %s\n"
                             "%s %s\n",
                             TEXT_VERSION, XSANE_VERSION,
                             XSANE_COPYRIGHT_SIGN, XSANE_COPYRIGHT_TXT,
                             TEXT_HOMEPAGE, XSANE_HOMEPAGE,
                             TEXT_EMAIL_ADR, XSANE_EMAIL_ADR);

  label = gtk_label_new(buf);
  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
  gtk_widget_show(label);


#ifdef HAVE_GTK2
  button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
#else
  button = gtk_button_new_with_label(BUTTON_CLOSE);
#endif
  gtk_widget_add_accelerator(button, "clicked", accelerator_group, GDK_Escape, (GdkModifierType)0, DEF_GTK_ACCEL_LOCKED);  
  GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_close_about_dialog_callback, NULL);
  gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
  gtk_widget_grab_default(button);
  gtk_widget_show(button);

  gtk_widget_show(about_dialog);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static GtkWidget *about_translation_dialog = NULL;

static int xsane_close_about_translation_dialog_callback(GtkWidget *widget, gpointer data)
{
  DBG(DBG_proc, "xsane_close_about_translation_dialog_callback\n");

  gtk_widget_destroy(about_translation_dialog);
  about_translation_dialog = NULL;

 return FALSE;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_about_translation_dialog(GtkWidget *widget, gpointer data)
{
 GtkWidget *vbox, *hbox, *button, *label;
 char buf[1024];
 char filename[PATH_MAX];
 GtkWidget *pixmapwidget;
 GdkBitmap *mask;
 GdkPixmap *pixmap;
 GtkStyle *style;
 GdkColor *bg_trans;
 GtkAccelGroup *accelerator_group;

  DBG(DBG_proc, "xsane_about_translation_dialog\n");

  if (about_translation_dialog)
  {
    return;
  }

  about_translation_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(about_translation_dialog), GTK_WIN_POS_CENTER);
  gtk_window_set_resizable(GTK_WINDOW(about_translation_dialog), FALSE);
  g_signal_connect(GTK_OBJECT(about_translation_dialog), "destroy", GTK_SIGNAL_FUNC(xsane_close_about_translation_dialog_callback), NULL);
  snprintf(buf, sizeof(buf), "%s %s", WINDOW_ABOUT_TRANSLATION, xsane.prog_name);
  gtk_window_set_title(GTK_WINDOW(about_translation_dialog), buf);
  gtk_container_set_border_width(GTK_CONTAINER(about_translation_dialog), 5);

  accelerator_group = gtk_accel_group_new();
  gtk_window_add_accel_group(GTK_WINDOW(about_translation_dialog), accelerator_group); 

  xsane_set_window_icon(about_translation_dialog, 0);

  hbox = gtk_hbox_new(/* not homogeneous */ TRUE, 5);
  gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
  gtk_container_add(GTK_CONTAINER(about_translation_dialog), hbox);
  gtk_widget_show(hbox);

  vbox = gtk_vbox_new(/* not homogeneous */ FALSE, 5);
  gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 20);
  gtk_widget_show(vbox);

  /* xsane logo */
  gtk_widget_realize(about_translation_dialog);

  style = gtk_widget_get_style(about_translation_dialog);
  bg_trans = &style->bg[GTK_STATE_NORMAL];

  xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-logo", 0, ".xpm", XSANE_PATH_SYSTEM);
  pixmap = gdk_pixmap_create_from_xpm(about_translation_dialog->window, &mask, bg_trans, filename);
  pixmapwidget = gtk_image_new_from_pixmap(pixmap, mask);
  gtk_box_pack_start(GTK_BOX(vbox), pixmapwidget, FALSE, FALSE, 2);
  gtk_widget_show(pixmapwidget);
  gdk_drawable_unref(pixmap);
  gdk_drawable_unref(mask);


  xsane_separator_new(vbox, 5);

  snprintf(buf, sizeof(buf), "%s\n"
                             "%s",
                             TEXT_TRANSLATION,
                             TEXT_TRANSLATION_INFO);

  label = gtk_label_new(buf);
  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
  gtk_widget_show(label);


#ifdef HAVE_GTK2
  button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
#else
  button = gtk_button_new_with_label(BUTTON_CLOSE);
#endif
  gtk_widget_add_accelerator(button, "clicked", accelerator_group, GDK_Escape, (GdkModifierType)0, DEF_GTK_ACCEL_LOCKED);  
  GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_close_about_translation_dialog_callback, NULL);
  gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
  gtk_widget_grab_default(button);
  gtk_widget_show(button);

  gtk_widget_show(about_translation_dialog);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_pref_toggle_tooltips(GtkWidget *widget, gpointer data)
{
  DBG(DBG_proc, "xsane_pref_toggle_tooltips\n");

  preferences.tooltips_enabled = (GTK_CHECK_MENU_ITEM(widget)->active != 0);
  xsane_back_gtk_set_tooltips(preferences.tooltips_enabled);
  xsane_pref_save();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_show_eula(GtkWidget *widget, gpointer data)
{
  xsane_display_eula(0);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_show_gpl(GtkWidget *widget, gpointer data)
{
  xsane_display_gpl();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_show_doc_via_nsr(GtkWidget *widget, gpointer data) /* show via netscape remote */
{
 char *name = (char *) data;
 char buf[TEXTBUFSIZE];
 pid_t pid;
 char *arg[5];
 struct stat st;
 char netscape_lock_path[PATH_MAX];

  DBG(DBG_proc, "xsane_show_doc_via_nsr(%s)\n", name);


  /* at first we have to test if netscape is running */
  /* a simple way is to take a look at ~/.netscape/lock */
  /* when this is a link we can assume that netscape is running */

  if (getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)) != NULL) /* $HOME defined? */
  {
    snprintf(netscape_lock_path, sizeof(netscape_lock_path), "%s%c.netscape%clock",
             getenv(STRINGIFY(ENVIRONMENT_HOME_DIR_NAME)), SLASH, SLASH); 
  }
  else
  {
    *netscape_lock_path = 0; /* empty path */
  }

#ifdef HAVE_LSTAT
  if ((strlen(netscape_lock_path) > 0) && (lstat(netscape_lock_path, &st) == 0)) /*  netscape is running */
#else
  if ((strlen(netscape_lock_path) > 0) && (stat(netscape_lock_path, &st) == 0)) /*  netscape is running */
#endif
  {
    DBG(DBG_proc, "xsane_show_doc_via_nsr: netscape is running\n");
    snprintf(buf, sizeof(buf), "openFile(%s, new-window)", name);
    arg[0] = (char *)"netscape";
    arg[1] = (char *)"-no-about-splash";
    arg[2] = (char *)"-remote";
    arg[3] = (char *)buf;
    arg[4] = 0;
 
    pid = fork();
 
    if (pid == 0) /* new process */
    {
     FILE *ipc_file = NULL;

      if (xsane.ipc_pipefd[0])
      {
        close(xsane.ipc_pipefd[0]); /* close reading end of pipe */
        ipc_file = fdopen(xsane.ipc_pipefd[1], "w");
      }

      DBG(DBG_info, "trying to change user id for new subprocess:\n");
      DBG(DBG_info, "old effective uid = %d\n", (int) geteuid());
      setuid(getuid());
      DBG(DBG_info, "new effective uid = %d\n", (int) geteuid());

      execvp(arg[0], arg); /* does not return if successfully */
      DBG(DBG_error, "%s %s\n", ERR_FAILED_EXEC_DOC_VIEWER, preferences.browser);

      /* send error message via IPC pipe to parent process */
      if (ipc_file)
      {
        fprintf(ipc_file, "%s %s:\n%s", ERR_FAILED_EXEC_DOC_VIEWER, preferences.browser, strerror(errno));
        fflush(ipc_file); /* make sure message is displayed */
        fclose(ipc_file);
      }

      _exit(0); /* do not use exit() here! otherwise gtk gets in trouble */
    }
    else /* parent process */
    {
      xsane_front_gtk_add_process_to_list(pid); /* add pid to child process list */
    }
  }
  else /* netscape not running */
  { 
    DBG(DBG_proc, "xsane_show_doc_via_nsr: netscape is not running, trying to start netscape\n");
    arg[0] = (char *)"netscape";
    arg[1] = (char *)name;
    arg[2] = 0;
 
    pid = fork();
 
    if (pid == 0) /* new process */
    {
     FILE *ipc_file = NULL;

      if (xsane.ipc_pipefd[0])
      {
        close(xsane.ipc_pipefd[0]); /* close reading end of pipe */
        ipc_file = fdopen(xsane.ipc_pipefd[1], "w");
      }

      DBG(DBG_info, "trying to change user id for new subprocess:\n");
      DBG(DBG_info, "old effective uid = %d\n", (int) geteuid());
      setuid(getuid());
      DBG(DBG_info, "new effective uid = %d\n", (int) geteuid());

      execvp(arg[0], arg); /* does not return if successfully */
      DBG(DBG_error, "%s %s\n", ERR_FAILED_EXEC_DOC_VIEWER, preferences.browser);

      /* send error message via IPC pipe to parent process */
      if (ipc_file)
      {
        fprintf(ipc_file, "%s %s:\n%s", ERR_FAILED_EXEC_DOC_VIEWER, preferences.browser, strerror(errno));
        fflush(ipc_file); /* make sure message is displayed */
        fclose(ipc_file);
      }

      _exit(0); /* do not use exit() here! otherwise gtk gets in trouble */
    }
    else /* parent process */
    {
      xsane_front_gtk_add_process_to_list(pid); /* add pid to child process list */
    }
  }

  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */
#if 0
/* may be used to parse command lines for execvp like popen does  */
static char **xsane_parse_command(char *command_line, char *url)
{
 char **argv = NULL;
 char command[1024];
 char *command_pos = command;
 char *arg_end;
 int size = 0;

  snprintf(command, sizeof(command), command_line, url);

  argv = malloc(sizeof(char *) * 2);

  while (command_pos)
  {
    argv = realloc(argv, sizeof(char *) * (size+2));

    arg_end = strchr(command_pos, ' ');
    if (arg_end)
    {
      *arg_end = 0;
    }

    argv[size] = strdup(command_pos);
 
    if (arg_end)
    {
      command_pos = arg_end + 1;
    }
    else
    {
      command_pos = NULL;
    }
   
    size++;
  }

  argv[size] = NULL;

  free(command);

 return (argv);
}
#endif

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_show_doc(GtkWidget *widget, gpointer data)
{
 char *name = (char *) data;
 char path[PATH_MAX];
 pid_t pid;
 char *arg[3];
 struct stat st;
 char *language_dir = NULL;

  DBG(DBG_proc, "xsane_show_doc(%s)\n", name);

  /* translation of language_dir gives the name of the subdirectory in */
  /* which there may be a translation of a documentation */
  language_dir = XSANE_LANGUAGE_DIR;
  snprintf(path, sizeof(path), "%s/%s/%s-doc.html", STRINGIFY(PATH_XSANE_DOC_DIR), language_dir, name);  
  if (stat(path, &st) != 0) /* test if file does exist */
  {
    snprintf(path, sizeof(path), "%s/%s-doc.html", STRINGIFY(PATH_XSANE_DOC_DIR), name); /* no, we use original doc */
  }

  if (!strcmp(preferences.browser, BROWSER_NETSCAPE))
  {
    xsane_show_doc_via_nsr(widget, (void *) path);
  }
  else
  {
    arg[0] = preferences.browser;
    arg[1] = path;
    arg[2] = 0;

    pid = fork();

    if (pid == 0) /* new process */
    { 
     FILE *ipc_file = NULL;

      if (xsane.ipc_pipefd[0])
      {
        close(xsane.ipc_pipefd[0]); /* close reading end of pipe */
        ipc_file = fdopen(xsane.ipc_pipefd[1], "w");
      }

      DBG(DBG_info, "trying to change user id for new subprocess:\n");
      DBG(DBG_info, "old effective uid = %d\n", (int) geteuid());
      setuid(getuid());
      DBG(DBG_info, "new effective uid = %d\n", (int) geteuid());

      DBG(DBG_info, "executing %s %s\n", arg[0], arg[1]);
      execvp(arg[0], arg); /* does not return if successfully */
      DBG(DBG_error, "%s %s\n", ERR_FAILED_EXEC_DOC_VIEWER, preferences.browser);

      /* send error message via IPC pipe to parent process */
      if (ipc_file)
      {
        fprintf(ipc_file, "%s %s:\n%s", ERR_FAILED_EXEC_DOC_VIEWER, preferences.browser, strerror(errno));
        fflush(ipc_file); /* make sure message is displayed */
        fclose(ipc_file);
      }

      _exit(0); /* do not use exit() here! otherwise gtk gets in trouble */
    }
    else /* parent process */
    {
      xsane_front_gtk_add_process_to_list(pid); /* add pid to child process list */
    }
  }
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_scan_callback(void)
{
  xsane.scan_rotation = xsane.preview->rotation;
  xsane_scan_dialog(NULL);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

GtkWidget *xsane_view_build_menu(void)
{
 GtkWidget *menu, *item, *submenu, *subitem;

  DBG(DBG_proc, "xsane_view_build_menu\n");

  menu = gtk_menu_new();
  gtk_menu_set_accel_group(GTK_MENU(menu), xsane.accelerator_group);

  /* show tooltips */

  item = gtk_check_menu_item_new_with_label(MENU_ITEM_SHOW_TOOLTIPS);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_T, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), preferences.tooltips_enabled);
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);
  g_signal_connect(GTK_OBJECT(item), "toggled", (GtkSignalFunc) xsane_pref_toggle_tooltips, NULL);


  /* insert separator: */

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);


  /* show resolution list */

  xsane.show_resolution_list_widget = gtk_check_menu_item_new_with_label(MENU_ITEM_SHOW_RESOLUTIONLIST);
  gtk_widget_add_accelerator(xsane.show_resolution_list_widget, "activate", xsane.accelerator_group, GDK_L, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.show_resolution_list_widget), preferences.show_resolution_list);
  gtk_menu_append(GTK_MENU(menu), xsane.show_resolution_list_widget);
  gtk_widget_set_sensitive(xsane.show_resolution_list_widget, FALSE); /* we only make it active when we have a resolution range */
  gtk_widget_show(xsane.show_resolution_list_widget);
  g_signal_connect(GTK_OBJECT(xsane.show_resolution_list_widget), "toggled", (GtkSignalFunc) xsane_show_resolution_list_callback, NULL);


  /* insert separator: */

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);


  /* update policy */

  item = gtk_menu_item_new_with_label(MENU_ITEM_UPDATE_POLICY);
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);

  submenu = gtk_menu_new();

  subitem = gtk_check_menu_item_new_with_label(SUBMENU_ITEM_POLICY_CONTINUOUS);
  gtk_menu_append(GTK_MENU(submenu), subitem);
  if (preferences.gtk_update_policy == GTK_UPDATE_CONTINUOUS)
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(subitem), TRUE);
  }
  g_signal_connect(GTK_OBJECT(subitem), "toggled", (GtkSignalFunc) xsane_set_update_policy_callback, (void *) GTK_UPDATE_CONTINUOUS);
  gtk_widget_show(subitem);
  xsane.update_policy_continu = subitem;

  subitem = gtk_check_menu_item_new_with_label(SUBMENU_ITEM_POLICY_DISCONTINU);
  gtk_menu_append(GTK_MENU(submenu), subitem);
  if (preferences.gtk_update_policy == GTK_UPDATE_DISCONTINUOUS)
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(subitem), TRUE);
  }
  g_signal_connect(GTK_OBJECT(subitem), "toggled", (GtkSignalFunc) xsane_set_update_policy_callback, (void *) GTK_UPDATE_DISCONTINUOUS);
  gtk_widget_show(subitem);
  xsane.update_policy_discont = subitem;

  subitem = gtk_check_menu_item_new_with_label(SUBMENU_ITEM_POLICY_DELAYED);
  gtk_menu_append(GTK_MENU(submenu), subitem);
  if (preferences.gtk_update_policy == GTK_UPDATE_DELAYED)
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(subitem), TRUE);
  }
  g_signal_connect(GTK_OBJECT(subitem), "toggled", (GtkSignalFunc) xsane_set_update_policy_callback, (void *) GTK_UPDATE_DELAYED);
  gtk_widget_show(subitem);
  xsane.update_policy_delayed = subitem;

  gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);


  /* insert separator: */

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);


  /* length unit */

  item = gtk_menu_item_new_with_label(MENU_ITEM_LENGTH_UNIT);
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);

  submenu = gtk_menu_new();

  subitem = gtk_check_menu_item_new_with_label(SUBMENU_ITEM_LENGTH_MILLIMETERS);
  gtk_menu_append(GTK_MENU(submenu), subitem);
  if ( (preferences.length_unit > 0.9) && (preferences.length_unit < 1.1))
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(subitem), TRUE);
  }
  g_signal_connect(GTK_OBJECT(subitem), "toggled", (GtkSignalFunc) xsane_set_pref_unit_callback, (void *)"mm");
  gtk_widget_show(subitem);
  xsane.length_unit_mm = subitem;

  subitem = gtk_check_menu_item_new_with_label(SUBMENU_ITEM_LENGTH_CENTIMETERS);
  gtk_menu_append(GTK_MENU(submenu), subitem);
  if ( (preferences.length_unit > 9.9) && (preferences.length_unit < 10.1))
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(subitem), TRUE);
  }
  g_signal_connect(GTK_OBJECT(subitem), "toggled", (GtkSignalFunc) xsane_set_pref_unit_callback, (void *)"cm");
  gtk_widget_show(subitem);
  xsane.length_unit_cm = subitem;

  subitem = gtk_check_menu_item_new_with_label(SUBMENU_ITEM_LENGTH_INCHES);
  gtk_menu_append(GTK_MENU(submenu), subitem);
  if ( (preferences.length_unit > 25.3) && (preferences.length_unit < 25.5))
  {
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(subitem), TRUE);
  }
  g_signal_connect(GTK_OBJECT(subitem), "toggled", (GtkSignalFunc) xsane_set_pref_unit_callback, (void *)"in");
  gtk_widget_show(subitem);
  xsane.length_unit_in = subitem;

  gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);

  return menu;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

GtkWidget *xsane_window_build_menu(void)
{
 GtkWidget *menu;

  DBG(DBG_proc, "xsane_window_build_menu\n");

  menu = gtk_menu_new();
  gtk_menu_set_accel_group(GTK_MENU(menu), xsane.accelerator_group);


  /* show gamma */

  xsane.show_gamma_widget = gtk_check_menu_item_new_with_label(MENU_ITEM_SHOW_GAMMA);
  gtk_widget_add_accelerator(xsane.show_gamma_widget, "activate", xsane.accelerator_group, GDK_3, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.show_gamma_widget), preferences.show_gamma);
  gtk_menu_append(GTK_MENU(menu), xsane.show_gamma_widget);
  gtk_widget_show(xsane.show_gamma_widget);
  g_signal_connect(GTK_OBJECT(xsane.show_gamma_widget), "toggled", (GtkSignalFunc) xsane_show_gamma_callback, NULL);

  /* show batch_scan */

  xsane.show_batch_scan_widget = gtk_check_menu_item_new_with_label(MENU_ITEM_SHOW_BATCH_SCAN);
  gtk_widget_add_accelerator(xsane.show_batch_scan_widget, "activate", xsane.accelerator_group, GDK_4, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.show_batch_scan_widget), preferences.show_batch_scan);
  gtk_menu_append(GTK_MENU(menu), xsane.show_batch_scan_widget);
  gtk_widget_show(xsane.show_batch_scan_widget);
  g_signal_connect(GTK_OBJECT(xsane.show_batch_scan_widget), "toggled", (GtkSignalFunc) xsane_show_batch_scan_callback, NULL);
  
  /* show device options */

  xsane.show_device_options_widget = gtk_check_menu_item_new_with_label(MENU_ITEM_SHOW_DEVICEOPTIONS);
  gtk_widget_add_accelerator(xsane.show_device_options_widget, "activate", xsane.accelerator_group, GDK_5, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(xsane.show_device_options_widget), preferences.show_device_options);
  gtk_menu_append(GTK_MENU(menu), xsane.show_device_options_widget);
  gtk_widget_show(xsane.show_device_options_widget);
  g_signal_connect(GTK_OBJECT(xsane.show_device_options_widget), "toggled", (GtkSignalFunc) xsane_show_device_options_callback, NULL);

  return menu;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_device_preferences_load(void)
{
 char filename[PATH_MAX];
 char windowname[TEXTBUFSIZE];

  DBG(DBG_proc, "xsane_device_preferences_load\n");

  xsane_set_sensitivity(FALSE);

  sprintf(windowname, "%s %s %s", xsane.prog_name, WINDOW_LOAD_SETTINGS, xsane.device_text);
  xsane_back_gtk_make_path(sizeof(filename), filename, "xsaner", 0, 0, xsane.device_set_filename, ".drc", XSANE_PATH_LOCAL_SANE);

  if (!xsane_back_gtk_get_filename(windowname, filename, sizeof(filename), filename, NULL, NULL, XSANE_FILE_CHOOSER_ACTION_OPEN, FALSE, XSANE_FILE_FILTER_ALL | XSANE_FILE_FILTER_DRC, XSANE_FILE_FILTER_DRC))
  {
    xsane_device_preferences_load_file(filename);

    xsane_restore_windows_sizes();

    xsane_update_param(0);
    xsane_set_all_resolutions(); /* XXX test XXX */
    xsane_refresh_dialog();
    xsane_enhancement_by_gamma();
  }
  xsane_set_sensitivity(TRUE);
  xsane_update_histogram(TRUE /* update raw */);
}        

/* ---------------------------------------------------------------------------------------------------------------------- */

GtkWidget *xsane_preferences_build_menu(void)
{
 GtkWidget *menu, *item;

  DBG(DBG_proc, "xsane_preferences_build_menu\n");

  menu = gtk_menu_new();
  gtk_menu_set_accel_group(GTK_MENU(menu), xsane.accelerator_group);


  /* XSane setup dialog */

  item = gtk_menu_item_new_with_label(MENU_ITEM_SETUP);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_S, GDK_MOD1_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_setup_dialog, NULL);
  gtk_widget_show(item);

  /* insert separator: */

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);


#if 0
  /* change working directory */

  item = gtk_menu_item_new_with_label(MENU_ITEM_CHANGE_WORKING_DIR);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_D, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_change_working_directory, NULL);
  gtk_widget_show(item);

  /* insert separator: */

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);
#endif

#ifdef HAVE_LIBLCMS
  /* enable color management */

  item = gtk_check_menu_item_new_with_label(MENU_ITEM_ENABLE_COLOR_MANAGEMENT);
  g_signal_connect(GTK_OBJECT(item), "toggled", (GtkSignalFunc) xsane_enable_color_management_callback, NULL);
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);
  xsane.enable_color_management_widget = item;
#endif


  /* edit medium definitions */

  item = gtk_check_menu_item_new_with_label(MENU_ITEM_EDIT_MEDIUM_DEF);
  g_signal_connect(GTK_OBJECT(item), "toggled", (GtkSignalFunc) xsane_edit_medium_definition_callback, NULL);
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);
  xsane.edit_medium_definition_widget = item;

  /* insert separator: */

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);

  /* Save device setting */

  item = gtk_menu_item_new_with_label(MENU_ITEM_SAVE_DEVICE_SETTINGS);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_P, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_device_preferences_save, NULL);
  gtk_widget_show(item);

  /* Load device setting */

  item = gtk_menu_item_new_with_label(MENU_ITEM_LOAD_DEVICE_SETTINGS);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_G, GDK_CONTROL_MASK, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_device_preferences_load, NULL);
  gtk_widget_show(item);

  return menu;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

GtkWidget *xsane_help_build_menu(void)
{
 GtkWidget *menu, *item;

  DBG(DBG_proc, "xsane_help_build_menu\n");

  menu = gtk_menu_new();
  gtk_menu_set_accel_group(GTK_MENU(menu), xsane.accelerator_group);



  /* XSane doc -> html viewer */

  item = gtk_menu_item_new_with_label(MENU_ITEM_XSANE_DOC);
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_show_doc, (void *) "sane-xsane");
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_F1, (GdkModifierType)0, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_widget_show(item);


  /* separator */

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);


  /* Backend doc -> html viewer */

  if (xsane.backend_name)
  {
    item = gtk_menu_item_new_with_label(MENU_ITEM_BACKEND_DOC);
    gtk_menu_append(GTK_MENU(menu), item);
    g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_show_doc, (void *) xsane.backend_name);
    gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_F2, (GdkModifierType)0, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
    gtk_widget_show(item);
  }


  /* available backends -> html viewer */

  item = gtk_menu_item_new_with_label(MENU_ITEM_AVAILABLE_BACKENDS);
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_show_doc, (void *) "sane-backends");
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_F3, (GdkModifierType)0, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_widget_show(item);

  
  /* problems -> html viewer */

  item = gtk_menu_item_new_with_label(MENU_ITEM_PROBLEMS);
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_show_doc, (void *) "sane-problems");
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_F4, (GdkModifierType)0, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_widget_show(item);

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);


  /* scantips -> html viewer */

  item = gtk_menu_item_new_with_label(MENU_ITEM_SCANTIPS);
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_show_doc, (void *) "sane-scantips");
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_F5, (GdkModifierType)0, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_widget_show(item);


  /* separator */

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);


  /* XSane about dialog */

  item = gtk_menu_item_new_with_label(MENU_ITEM_ABOUT_XSANE);
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_about_dialog, NULL);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_F6, (GdkModifierType)0, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_widget_show(item);

  /* XSane about translation dialog */

  item = gtk_menu_item_new_with_label(MENU_ITEM_ABOUT_TRANSLATION);
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_about_translation_dialog, NULL);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_F7, (GdkModifierType)0, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_widget_show(item);


  /* separator */

  item = gtk_menu_item_new();
  gtk_menu_append(GTK_MENU(menu), item);
  gtk_widget_show(item);


  /* XSane eula */

  item = gtk_menu_item_new_with_label(MENU_ITEM_XSANE_EULA);
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_show_eula, NULL);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_F8, (GdkModifierType)0, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_widget_show(item);

  /* gpl */

  item = gtk_menu_item_new_with_label(MENU_ITEM_XSANE_GPL);
  gtk_menu_append(GTK_MENU(menu), item);
  g_signal_connect(GTK_OBJECT(item), "activate", (GtkSignalFunc) xsane_show_gpl, NULL);
  gtk_widget_add_accelerator(item, "activate", xsane.accelerator_group, GDK_F9, (GdkModifierType)0, (GtkAccelFlags)(GTK_ACCEL_VISIBLE | DEF_GTK_ACCEL_LOCKED));
  gtk_widget_show(item);


  return menu;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_choose_dialog_ok_callback(void)
{
  DBG(DBG_proc, "xsane_choose_dialog_ok_callback\n");

  g_signal_handlers_disconnect_by_func(GTK_OBJECT(xsane.choose_device_dialog), (void *)GTK_SIGNAL_FUNC(xsane_exit), 0);
  gtk_widget_destroy(xsane.choose_device_dialog);
  xsane_build_ui();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_select_device_by_key_callback(GtkWidget * widget, gpointer data)
{
  DBG(DBG_proc, "xsane_select_device_by_key_callback\n");

  xsane.selected_dev = (long) data;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_select_device_by_mouse_callback(GtkWidget * widget, GdkEventButton *event, gpointer data)
{
  DBG(DBG_proc, "xsane_select_device_by_mouse_callback\n");

  xsane.selected_dev = (long) data;
  if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
  {
    xsane_choose_dialog_ok_callback();
  }

 return 0;
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_choose_device(void)
{
 GtkWidget *main_vbox, *vbox, *hbox, *button_box, *button, *device_frame, *device_vbox, *pixmapwidget, *label;
 GdkBitmap *mask = NULL;
 GdkPixmap *pixmap = NULL;
 GtkStyle *style;
 GdkColor *bg_trans;
 GSList *owner;
 GtkAccelGroup *device_selection_accelerator_group;
 gint i;
 const SANE_Device *adev;
 char buf[TEXTBUFSIZE];
 char vendor[12];
 char model[17];
 char type[20];
 char filename[PATH_MAX];
 size_t j;
 char *xsane_default_device = NULL;
 int ndevs;

#define TEXT_NO_VENDOR "no vendor\0"
#define TEXT_NO_MODEL  "no model\0"
#define TEXT_NO_TYPE   "no type\0"

  DBG(DBG_proc, "xsane_choose_device\n");

  xsane_default_device = getenv(XSANE_DEFAULT_DEVICE);
  if (xsane_default_device)
  {
    for (ndevs = 0; xsane.devlist[ndevs]; ++ndevs)
    {
      if (!strncmp(xsane.devlist[ndevs]->name, xsane_default_device, strlen(xsane_default_device)))
      {
        xsane.selected_dev = ndevs;
        break; 
      }
    }
  }

  xsane.choose_device_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(xsane.choose_device_dialog), GTK_WIN_POS_CENTER);
  gtk_window_set_resizable(GTK_WINDOW(xsane.choose_device_dialog), FALSE);
  g_signal_connect(GTK_OBJECT(xsane.choose_device_dialog), "destroy", GTK_SIGNAL_FUNC(xsane_exit), NULL);
  snprintf(buf, sizeof(buf), "%s %s %s", xsane.prog_name, XSANE_VERSION, WINDOW_DEVICE_SELECTION);
  gtk_window_set_title(GTK_WINDOW(xsane.choose_device_dialog), buf);

  device_selection_accelerator_group = gtk_accel_group_new(); /* do we have to delete it when dialog is closed ? */
  gtk_window_add_accel_group(GTK_WINDOW(xsane.choose_device_dialog), device_selection_accelerator_group); 

  main_vbox = gtk_vbox_new(FALSE, 12);
  gtk_container_set_border_width(GTK_CONTAINER(main_vbox), 12);
  gtk_container_add(GTK_CONTAINER(xsane.choose_device_dialog), main_vbox);
  gtk_widget_show(main_vbox);

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(vbox), 0);
  gtk_box_pack_start(GTK_BOX(main_vbox), vbox, TRUE, TRUE, 0);
  gtk_widget_show(vbox);

  /* xsane logo */
  gtk_widget_realize(xsane.choose_device_dialog);

  style = gtk_widget_get_style(xsane.choose_device_dialog);
  bg_trans = &style->bg[GTK_STATE_NORMAL];

  xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-logo", 0, ".xpm", XSANE_PATH_SYSTEM);
  pixmap = gdk_pixmap_create_from_xpm(xsane.choose_device_dialog->window, &mask, bg_trans, filename);
  pixmapwidget = gtk_image_new_from_pixmap(pixmap, mask);
  gtk_box_pack_start(GTK_BOX(vbox), pixmapwidget, FALSE, FALSE, 0);
  gtk_widget_show(pixmapwidget);
  gdk_drawable_unref(pixmap);
  gdk_drawable_unref(mask);

  xsane_set_window_icon(xsane.choose_device_dialog, (gchar **) 0);

  snprintf(buf, sizeof(buf), "%s %s\n", XSANE_COPYRIGHT_SIGN, XSANE_COPYRIGHT_TXT);
  label = gtk_label_new(buf);
  gtk_widget_show(label);
  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);


  /* list the drivers with radiobuttons */
  device_frame = gtk_frame_new(TEXT_AVAILABLE_DEVICES);
  gtk_container_set_border_width(GTK_CONTAINER(device_frame), 0);
  gtk_box_pack_start(GTK_BOX(vbox), device_frame, FALSE, FALSE, 0);
  gtk_widget_show(device_frame);

  device_vbox = gtk_vbox_new(FALSE, 5);
  gtk_container_set_border_width(GTK_CONTAINER(device_vbox), 6);
  gtk_container_add(GTK_CONTAINER(device_frame), device_vbox);

  owner = 0;
  for (i = 0; i < xsane.num_of_devs; i++)
  {
    adev = xsane.devlist[i];

    if (adev->vendor)
    {
      strncpy(vendor, adev->vendor, sizeof(vendor)-1);
    }
    else
    {
      strncpy(vendor, TEXT_NO_VENDOR, sizeof(vendor)-1);
    }

    vendor[sizeof(vendor)-1] = 0;
    for (j = strlen(vendor); j < sizeof(vendor)-1; j++)
    {
      vendor[j] = ' ';
    }

    if (adev->model)
    {
      strncpy(model, adev->model, sizeof(model)-1);
    }
    else
    {
      strncpy(model, TEXT_NO_MODEL, sizeof(model)-1);
    }

    model[sizeof(model)-1] = 0;
    for (j = strlen(model); j < sizeof(model)-1; j++)
    {
      model[j] = ' ';
    }

    if (adev->type)
    {
      strncpy(type, _(adev->type), sizeof(type)-1); /* allow translation of device type */
    }
    else
    {
      strncpy(type, TEXT_NO_TYPE, sizeof(type)-1);
    }

    type[sizeof(type)-1] = 0;
    for (j = strlen(type); j < sizeof(type)-1; j++)
    {
      type[j] = ' ';
    }

    snprintf(buf, sizeof(buf), "%s %s %s [%s]", vendor, model, type, adev->name);
    button = gtk_radio_button_new_with_label(owner, (char *) buf);

    if (i<12)
    {
      gtk_widget_add_accelerator(button, "clicked", device_selection_accelerator_group, GDK_F1+i, (GdkModifierType)0, DEF_GTK_ACCEL_LOCKED);
    }

    g_signal_connect(GTK_OBJECT(button), "button_press_event", (GtkSignalFunc) xsane_select_device_by_mouse_callback, (void *) (long) i);
    g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_select_device_by_key_callback, (void *) (long) i);
    gtk_box_pack_start(GTK_BOX(device_vbox), button, TRUE, TRUE, 0);
    gtk_widget_show(button);
    owner = gtk_radio_button_group(GTK_RADIO_BUTTON(button));;

    if (i == xsane.selected_dev)
    {
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (button), TRUE);
    }
  }
  gtk_widget_show(device_vbox);


  /* The bottom row of buttons */
  hbox = gtk_hbox_new(FALSE, 0);
  gtk_box_pack_end(GTK_BOX(main_vbox), hbox, FALSE, FALSE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(hbox), 0);
  gtk_widget_show(hbox);

  button_box = gtk_hbox_new(TRUE, 0);
  gtk_box_pack_end(GTK_BOX(hbox), button_box, FALSE, FALSE, 0);
  gtk_widget_show(button_box);

  /* The Cancel button */
#ifdef HAVE_GTK2
  button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
#else
  button = gtk_button_new_with_label(BUTTON_CANCEL);
#endif
  gtk_widget_add_accelerator(button, "clicked", device_selection_accelerator_group, GDK_Escape, (GdkModifierType)0, DEF_GTK_ACCEL_LOCKED);
  g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_exit, NULL);
  gtk_box_pack_start(GTK_BOX(button_box), button, TRUE, TRUE, 6);
  gtk_widget_show(button);

  /* The OK button */
#ifdef HAVE_GTK2
  button = gtk_button_new_from_stock(GTK_STOCK_OK);
#else
  button = gtk_button_new_with_label(BUTTON_OK);
#endif
  GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  g_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) xsane_choose_dialog_ok_callback, NULL);
  gtk_box_pack_start(GTK_BOX(button_box), button, TRUE, TRUE, 0);
  gtk_widget_grab_default(button);
  gtk_widget_show(button);

  gtk_widget_show(xsane.choose_device_dialog);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_usage(void)
{
  g_print("XSane %s %s\n", TEXT_VERSION, XSANE_VERSION);
  g_print("%s %s\n\n", XSANE_COPYRIGHT_SIGN, XSANE_COPYRIGHT_TXT);
  g_print("%s %s %s\n\n", TEXT_USAGE, xsane.prog_name, TEXT_USAGE_OPTIONS);
  g_print("%s\n\n", TEXT_HELP);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static int xsane_init(int argc, char **argv)
/* returns 0 - if ok
           1 - if license was not accepted
           2 - if canceld because xsane was started as root
           3 - if wrong sane major version was found */
{
 GtkWidget *device_scanning_dialog;
 GtkWidget *main_vbox, *hbox;
 GdkPixmap *pixmap;
 GdkBitmap *mask;
 GtkWidget *pixmapwidget;
 GtkWidget *label;
 GtkWidget *frame;
 struct stat st;
 char filename[PATH_MAX];
 char buf[TEXTBUFSIZE];

  DBG(DBG_proc, "xsane_init\n");

#ifndef _WIN32
  gtk_set_locale();
#endif
  gtk_init(&argc, &argv);
  setlocale(LC_NUMERIC, "C");

#ifdef HAVE_ANY_GIMP
  gtk_rc_parse(gimp_gtkrc());

# ifdef HAVE_GIMP_2
  gdk_set_use_xshm(TRUE);
# else 
  gdk_set_use_xshm(gimp_use_xshm());
# endif

#endif

  /* before we open any windows we have to read the style file */
  xsane_back_gtk_make_path(sizeof(filename), filename, "xsaner", 0, "xsane-style", 0, ".rc", XSANE_PATH_LOCAL_SANE);
  if (stat(filename, &st) >= 0)
  {
    DBG(DBG_info, "loading %s\n", filename);
    gtk_rc_parse(filename);
  }
  else /* no local xsane-style.rc, look for system file */
  {
    xsane_back_gtk_make_path(sizeof(filename), filename, "xsane", 0, "xsane-style", 0, ".rc", XSANE_PATH_SYSTEM);
    if (stat(filename, &st) >= 0)
    {
      DBG(DBG_info, "loading %s\n", filename);
      gtk_rc_parse(filename);
    }
  }

  if (argc > 1)
  {
    int ch;

    while((ch = getopt_long(argc, argv, "cd:efghlmnpr:svFN:RV", long_options, 0)) != EOF)
    {
      switch(ch)
      {
        case 'g': /* This options is set when xsane is called from the */
                  /* GIMP. If xsane is compiled without GIMP support */
                  /* then you get the error message when GIMP does */
                  /* query or tries to start the xsane plugin! */
#ifndef HAVE_ANY_GIMP
          g_print("%s: %s\n", argv[0], ERR_GIMP_SUPPORT_MISSING);
          exit(0);
#endif
         break;

        case 'v': /* --version */
          g_print("%s-%s %s %s\n", xsane.prog_name, XSANE_VERSION, XSANE_COPYRIGHT_SIGN, XSANE_COPYRIGHT_TXT);
          g_print("  %s %s\n", TEXT_EMAIL_ADR, XSANE_EMAIL_ADR);
          g_print("  %s %s\n", TEXT_PACKAGE, XSANE_PACKAGE_VERSION);
          g_print("  %s%d.%d.%d\n", TEXT_GTK_VERSION, GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
#ifdef HAVE_LIBLCMS
          g_print("  %s\n", TEXT_WITH_CMS_FUNCTION);
#endif
#ifdef HAVE_ANY_GIMP
          g_print("  %s, %s%s\n", TEXT_WITH_GIMP_SUPPORT, TEXT_GIMP_VERSION, GIMP_VERSION);
#else
          g_print("  %s\n", TEXT_WITHOUT_GIMP_SUPPORT);
#endif

          g_print("  %s ", TEXT_OUTPUT_FORMATS);

#ifdef HAVE_LIBJPEG
          g_print("jpeg, ");
#endif

#ifdef HAVE_LIBZ
          g_print("pdf(compr.), ");
#else
          g_print("pdf, ");
#endif

#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
          g_print("png, ");
#endif
#endif

          g_print("pnm, ");
#ifdef HAVE_LIBZ
          g_print("ps(compr.)");
#else
          g_print("ps");
#endif

#ifdef SUPPORT_RGBA
          g_print(", rgba");
#endif

#ifdef HAVE_LIBTIFF
          g_print(", tiff");
#endif

          g_print(", txt");

          g_print("\n");
          exit(0);
         break;

        case 'l': /* --license */
          g_print("%s-%s %s %s\n\n", xsane.prog_name, XSANE_VERSION, XSANE_COPYRIGHT_SIGN, XSANE_COPYRIGHT_TXT);
          g_print("%s\n", TEXT_GPL);
          exit(0);
         break;

        case 'd': /* --device-settings */
          xsane.device_set_filename = strdup(optarg);
         break;

        case 'r': /* --xsane-rc */
          xsane.xsane_rc_set_filename = strdup(optarg);
         break;

        case 'V': /* --viewer, default */
          xsane.xsane_mode = XSANE_VIEWER;
         break;

        case 's': /* --save */
          xsane.xsane_mode = XSANE_SAVE;
         break;

        case 'c': /* --copy */
          xsane.xsane_mode = XSANE_COPY;
         break;

        case 'f': /* --fax */
          xsane.xsane_mode = XSANE_FAX;
         break;

        case 'm': /* --multipage */
          xsane.xsane_mode = XSANE_MULTIPAGE;
         break;

        case 'e': /* --email */
          xsane.xsane_mode = XSANE_EMAIL;
         break;

        case 'n': /* --No-mode-selection */
           xsane.mode_selection = 0;
         break;

        case 'N': /* --No-filenameselection filename */
           xsane.force_filename = TRUE;
           xsane.external_filename  = strdup(optarg);
         break;

        case 'p': /* --print-filenames */
           xsane.print_filenames = TRUE;
         break;

        case 'h': /* --help */
        default:
          xsane_usage();
          exit(0);
      }
    }
  }

  /* if no xsane_rc filename is given on commandline then use "xsane.rc" */
 
  if (!xsane.xsane_rc_set_filename)
  {
    xsane.xsane_rc_set_filename = strdup("xsaner"); /* ".rc" is appended later */
  }


  if (xsane_pref_restore()) /* restore preferences, returns TRUE if license is not accpted yet */
  {
    if (xsane_display_eula(1)) /* show license and ask for accept/not accept */
    {
      DBG(DBG_info, "user did not accept eula, we abort\n");
      return 1; /* User did not accept eula */
    }
    else /* User did accept eula */
    {
      xsane_pref_save();
    }
  }

  xsane_pref_restore_media();

#ifndef HAVE_OS2_H
  if (!getuid()) /* root ? */
  {
    if (xsane_back_gtk_decision(ERR_HEADER_WARNING, (gchar **) warning_xpm, WARN_XSANE_AS_ROOT,
        BUTTON_CANCEL, BUTTON_CONT_AT_OWN_RISK, TRUE /* wait */) == TRUE)
    {
      return 2; /* User selected CANCEL */
    } 
  }
#endif

  sane_init(&xsane.sane_backend_versioncode, xsane_authorization_callback);

  if (SANE_VERSION_MAJOR(xsane.sane_backend_versioncode) != SANE_V_MAJOR)
  {
    DBG(DBG_error0, "\n\n"
                    "%s %s:\n"
                    "  %s\n"
                    "  %s %d\n"
                    "  %s %d\n"
                    "%s\n\n",
                    xsane.prog_name, ERR_ERROR,
                    ERR_MAJOR_VERSION_NR_CONFLICT,
                    ERR_XSANE_MAJOR_VERSION, SANE_V_MAJOR,
                    ERR_BACKEND_MAJOR_VERSION, SANE_VERSION_MAJOR(xsane.sane_backend_versioncode),
                    ERR_PROGRAM_ABORTED);
    return 3;
  }

  device_scanning_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(device_scanning_dialog), GTK_WIN_POS_CENTER);
  gtk_window_set_resizable(GTK_WINDOW(device_scanning_dialog), FALSE);
/*
  gtk_window_set_deletable(GTK_WINDOW(device_scanning_dialog), FALSE);
*/
  snprintf(buf, sizeof(buf), "%s %s", xsane.prog_name, XSANE_VERSION);
  gtk_window_set_title(GTK_WINDOW(device_scanning_dialog), buf);
  g_signal_connect(GTK_OBJECT(device_scanning_dialog), "delete_event", GTK_SIGNAL_FUNC(xsane_quit), NULL);

  xsane_set_window_icon(device_scanning_dialog, 0);

  frame = gtk_frame_new(NULL);
  gtk_container_set_border_width(GTK_CONTAINER(frame), 10);
  gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_add(GTK_CONTAINER(device_scanning_dialog), frame);
  gtk_widget_show(frame);

  main_vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(main_vbox), 20);
  gtk_container_add(GTK_CONTAINER(frame), main_vbox);
  gtk_widget_show(main_vbox);

  hbox = gtk_hbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(main_vbox), hbox, FALSE, FALSE, 2);
  gtk_widget_show(hbox);

  /* add device icon */
  pixmap = gdk_pixmap_create_from_xpm_d(device_scanning_dialog->window, &mask, xsane.bg_trans, (gchar **) device_xpm);
  pixmapwidget = gtk_image_new_from_pixmap(pixmap, mask);
  gtk_box_pack_start(GTK_BOX(hbox), pixmapwidget, FALSE, FALSE, 10);
  gtk_widget_show(pixmapwidget);
  gdk_drawable_unref(pixmap);
  gdk_drawable_unref(mask);

  /* add text */
  snprintf(buf, sizeof(buf), "  %s  ", TEXT_SCANNING_DEVICES);
  label = gtk_label_new(buf);
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 2);
  gtk_widget_show(label);
  
  gtk_widget_show(device_scanning_dialog);

  /* wait 100 ms to make sure window is displayed */
  usleep(100000); /* this makes sure that the text "scanning for devices" is displayed */

  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }

  xsane_widget_test_uposition(device_scanning_dialog);

  /* wait 100 ms to make sure window is displayed */
  usleep(100000); /* this makes sure that the text "scanning for devices" is displayed */

  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }

  sane_get_devices(&xsane.devlist, SANE_FALSE /* local and network devices */);


  gtk_widget_destroy(device_scanning_dialog);

  while (gtk_events_pending())
  {
    gtk_main_iteration();
  }

  /* if devicename is given try to identify it, if not found, open device list */
  if (optind < argc) 
  {
   int ndevs;

    for (ndevs = 0; xsane.devlist[ndevs]; ++ndevs)
    {
      if (!strncmp(xsane.devlist[ndevs]->name, argv[argc - 1], strlen(argv[argc - 1])))
      {
        xsane.selected_dev = ndevs;
        break; 
      }
    }

    if ((xsane.selected_dev < 0) && (argc > 1))
    {
     static SANE_Device dev;
     static const SANE_Device *device_list[] = { &dev, 0 };

      memset(&dev, 0, sizeof(dev));
      dev.name   = argv[argc - 1];
      dev.vendor = TEXT_UNKNOWN;
      dev.type   = TEXT_UNKNOWN;
      dev.model  = TEXT_UNKNOWN;

      xsane.devlist = device_list;
      xsane.selected_dev = 0;
    }
  }

 return 0; /* everything is ok */
}

/* ---------------------------------------------------------------------------------------------------------------------- */

static void xsane_help_no_devices(void)
{
 char buf[1024];

  snprintf(buf, sizeof(buf), "%s\n\n%s", ERR_NO_DEVICES, HELP_NO_DEVICES);
  xsane_back_gtk_decision(WINDOW_NO_DEVICES, (gchar**) no_device_xpm, buf, BUTTON_CLOSE, NULL, TRUE);
}

/* ---------------------------------------------------------------------------------------------------------------------- */

void xsane_interface(int argc, char **argv)
{
 struct SIGACTION act;

  DBG(DBG_proc, "xsane_interface\n");

  xsane.info_label = NULL;

  if (xsane_init(argc, argv)) /* initialize xsane variables if command line option is given, set xsane.selected_dev */
  {
    return; /* we have to abort (license not accepted, aborted because xsane runs as root) */
  }

  for (xsane.num_of_devs = 0; xsane.devlist[xsane.num_of_devs]; ++xsane.num_of_devs); /* count available devices */

  if (xsane.selected_dev >= 0) /* device name is given on cammand line */
  {
    xsane_build_ui(); /* open device xsane.selected_dev */
  }
  else /* no device name given on command line */
  {
    if (xsane.num_of_devs > 0) /* devices available */
    {
      xsane.selected_dev = 0;
      if (xsane.num_of_devs == 1)
      {
        xsane_build_ui(); /* open device xsane.selected_dev */
      }
      else
      {
        xsane_choose_device(); /* open device selection window and get device */
      }
    }
    else /* xsane.num_of_devs == 0, no devices available */
    {
     char buf[TEXTBUFSIZE];

      snprintf(buf, sizeof(buf), "%s\n", ERR_NO_DEVICES);

      if (xsane_back_gtk_decision(WINDOW_NO_DEVICES, (gchar**) no_device_xpm, buf, BUTTON_HELP, BUTTON_CLOSE, TRUE))
      {
        xsane_help_no_devices();
      }
      xsane_exit();
    }
  }

  /* define SIGTERM, SIGINT, SIGHUP-handler to make sure that e.g. all temporary files are deleted */
  /* when xsane gets such a signal */
  memset(&act, 0, sizeof(act));
  act.sa_handler = xsane_quit_handler;
  sigaction(SIGTERM, &act, 0);
  sigaction(SIGINT,  &act, 0);
  sigaction(SIGHUP,  &act, 0);

  /* add a signal handler that cleans up zombie child processes */
  memset(&act, 0, sizeof(act));
  act.sa_handler = xsane_sigchld_handler;
  sigaction(SIGCHLD, &act, 0);

  gtk_main();
  sane_exit();
}

/* ---------------------------------------------------------------------------------------------------------------------- */

int main(int argc, char **argv)
{
  DBG_init();

  DBG(DBG_error, "This is xsane version %s\n", XSANE_VERSION);

  memset(&xsane, 0, sizeof(xsane)); /* set all values in xsane to 0 */

  umask(XSANE_DEFAULT_UMASK); /* define permissions of new files */ 

  xsane.selected_dev        = -1;	/* no selected device */

  xsane.sensitivity         = TRUE;

  xsane.mode                = XSANE_STANDALONE;
  xsane.xsane_mode          = -1;
  xsane.lineart_mode        = XSANE_LINEART_STANDARD;
  xsane.xsane_output_format = XSANE_PNM;
  xsane.mode_selection      = 1; /* enable selection of xsane mode */

  xsane.input_tag           = -1; /* no input tag */

  xsane.histogram_lines  = 1;

  xsane.zoom             = 1.0;
  xsane.resolution       = 72.0;
  xsane.resolution_x     = 72.0;
  xsane.resolution_y     = 72.0;
  xsane.copy_number      = 1;

  xsane.medium_shadow_gray     = 0.0;
  xsane.medium_shadow_red      = 0.0;
  xsane.medium_shadow_green    = 0.0;
  xsane.medium_shadow_blue     = 0.0;
  xsane.medium_highlight_gray  = 100.0;
  xsane.medium_highlight_red   = 100.0;
  xsane.medium_highlight_green = 100.0;
  xsane.medium_highlight_blue  = 100.0;
  xsane.medium_gamma_gray      = 1.0;
  xsane.medium_gamma_red       = 1.0;
  xsane.medium_gamma_green     = 1.0;
  xsane.medium_gamma_blue      = 1.0;
  xsane.medium_negative        = 0;
  xsane.medium_changed         = FALSE;

  xsane.brightness_min    = XSANE_BRIGHTNESS_MIN;
  xsane.brightness_max    = XSANE_BRIGHTNESS_MAX;
  xsane.contrast_gray_min = XSANE_CONTRAST_GRAY_MIN;
  xsane.contrast_min      = XSANE_CONTRAST_MIN;
  xsane.contrast_max      = XSANE_CONTRAST_MAX;

  xsane.gamma            = 1.0;
  xsane.gamma_red        = 1.0;
  xsane.gamma_green      = 1.0;
  xsane.gamma_blue       = 1.0;
  xsane.brightness       = 0.0;
  xsane.brightness_red   = 0.0;
  xsane.brightness_green = 0.0;
  xsane.brightness_blue  = 0.0;
  xsane.contrast         = 0.0;
  xsane.contrast_red     = 0.0;
  xsane.contrast_green   = 0.0;
  xsane.contrast_blue    = 0.0;
  xsane.threshold        = 50.0;

  xsane.slider_gray.value[2]  = 100.0;
  xsane.slider_gray.value[1]  = 50.0;
  xsane.slider_gray.value[0]  = 0.0;
  xsane.slider_red.value[2]   = 100.0;
  xsane.slider_red.value[1]   = 50.0;
  xsane.slider_red.value[0]   = 0.0;
  xsane.slider_green.value[2] = 100.0;
  xsane.slider_green.value[1] = 50.0;
  xsane.slider_green.value[0] = 0.0;
  xsane.slider_blue.value[2]  = 100.0;
  xsane.slider_blue.value[1]  = 50.0;
  xsane.slider_blue.value[0]  = 0.0;

  xsane.auto_white       = 100.0;
  xsane.auto_gray        = 50.0;
  xsane.auto_black       = 0.0;

  xsane.histogram_red    = 1;
  xsane.histogram_green  = 1;
  xsane.histogram_blue   = 1;
  xsane.histogram_int    = 1;
  xsane.histogram_log    = 0;

  xsane.xsane_channels          = -1; /* unused value to make sure that change of this vlaue is detected */
  xsane.scanner_gamma_color     = FALSE;
  xsane.scanner_gamma_gray      = FALSE;
  xsane.enhancement_rgb_default = TRUE;

  xsane.enable_color_management    = FALSE;

  xsane.adf_page_counter = 0;
  xsane.print_filenames  = FALSE;
  xsane.force_filename   = FALSE;

  xsane.batch_scan_load_default_list = TRUE; /* load default batch scan list at program startup */

  xsane.prog_name = strrchr(argv[0], '/');
  if (xsane.prog_name)
  {
    ++xsane.prog_name;
  }
  else
  {
    xsane.prog_name = argv[0];
  }

  if (!pipe(xsane.ipc_pipefd)) /* success */
  {
    DBG(DBG_info, "created ipc_pipefd for inter progress communication\n");
#ifndef BUGGY_GDK_INPUT_EXCEPTION
    gdk_input_add(xsane.ipc_pipefd[0], (GdkInputCondition)(GDK_INPUT_READ | GDK_INPUT_EXCEPTION), xsane_back_gtk_ipc_dialog_callback, 0);
#endif
  }
  else
  {
    DBG(DBG_info, "could not create pipe for inter progress communication\n");
    xsane.ipc_pipefd[0] = 0;
    xsane.ipc_pipefd[1] = 0;
  }

#if 0
  bindtextdomain(PACKAGE, STRINGIFY(LOCALEDIR));
  textdomain(PACKAGE);         
#ifdef HAVE_GTK2
  bind_textdomain_codeset(PACKAGE, "UTF-8");
#endif
#else
  DBG(DBG_info, "Setting xsane translation table with localedir: %s\n", STRINGIFY(LOCALEDIR));
  bindtextdomain(xsane.prog_name, STRINGIFY(LOCALEDIR));
  textdomain(xsane.prog_name);
#ifdef HAVE_GTK2
  bind_textdomain_codeset(xsane.prog_name, "UTF-8");
#endif
#endif

#ifdef HAVE_ANY_GIMP
  {
    GPrintFunc old_print_func;
    GPrintFunc old_printerr_func;
    int result;

    /* Temporarily install a print function that discards all output.
       This is to avoid annoying "you must run this program under
       gimp" messages when xsane gets invoked in stand-alone
       mode.  */
    old_print_func    = g_set_print_handler((GPrintFunc) null_print_func);
    old_printerr_func = g_set_printerr_handler((GPrintFunc) null_print_func);

#ifdef _WIN32 
    /* don`t know why, but win32 does need this */
    set_gimp_PLUG_IN_INFO_PTR(&PLUG_IN_INFO);
#endif

#ifdef HAVE_OS2_H
# ifndef HAVE_GTK2
    /* don`t know why, but os2 does need this one, a bit different to WIN32 */
    set_gimp_PLUG_IN_INFO(&PLUG_IN_INFO);
# endif
#endif

#ifdef HAVE_GIMP_2
    /* gimp_main() returns 1 if xsane wasn't invoked by GIMP */
    result = gimp_main(&PLUG_IN_INFO, argc, argv);
#else
    /* gimp_main() returns 1 if xsane wasn't invoked by GIMP */
    result = gimp_main(argc, argv);
#endif

#if 0
    /* this is the old version that seems to use the compatibility functions */
    g_set_message_handler(old_print_func);
    g_set_error_handler(old_printerr_func);
#else
    /* this is the new version that I think is the one that should be used */
    g_set_print_handler(old_print_func);
    g_set_printerr_handler(old_printerr_func);
#endif

    if (result)
    {
      xsane_interface(argc, argv);
    }
  }
#else
  xsane_interface(argc, argv);
#endif
  return 0;
}








/* ---------------------------------------------------------------------------------------------------------------------- */

